<!DOCTYPE html>
<html lang="zh-CN" dir="ltr">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>react+Node.js开发实战读书笔记 | Nuobel</title>
    <meta name="description" content="Nuobel 诺贝尔的源泉">
    <link rel="preload stylesheet" href="/assets/style.6550a0c8.css" as="style">
    <link rel="modulepreload" href="/assets/app.6facf8ba.js">
    <link rel="modulepreload" href="/assets/blog_readingNotes_reactAndNodeJS.md.8a99114b.lean.js">
    
    <meta name="theme-color" content="#3c8772">
  <script>var _hmt=_hmt||[];(function(){var e=document.createElement("script");e.src="https://hm.baidu.com/hm.js?b812ad2761f4f77a87ce9b80c0cae4a0";var t=document.getElementsByTagName("script")[0];t.parentNode.insertBefore(e,t)})();</script>
  <script id="check-dark-light">(()=>{const e=localStorage.getItem("vitepress-theme-appearance")||"",a=window.matchMedia("(prefers-color-scheme: dark)").matches;(!e||e==="auto"?a:e==="dark")&&document.documentElement.classList.add("dark")})();</script>
  </head>
  <body>
    <div id="app"><div class="Layout" data-v-93a960b4><!--[--><!--]--><!--[--><span tabindex="-1" data-v-151f2593></span><a href="#VPContent" class="VPSkipLink visually-hidden" data-v-151f2593> Skip to content </a><!--]--><!----><header class="VPNav" data-v-93a960b4 data-v-0fa0e57d><div class="VPNavBar has-sidebar" data-v-0fa0e57d data-v-be450ad9><div class="container" data-v-be450ad9><div class="title" data-v-be450ad9><div class="VPNavBarTitle has-sidebar" data-v-be450ad9 data-v-6d2fb2d9><a class="title" href="/" data-v-6d2fb2d9><!--[--><!--]--><!----><!--[-->Nuobel<!--]--><!--[--><!--]--></a></div></div><div class="content" data-v-be450ad9><div class="curtain" data-v-be450ad9></div><div class="content-body" data-v-be450ad9><!--[--><!--]--><!----><nav aria-labelledby="main-nav-aria-label" class="VPNavBarMenu menu" data-v-be450ad9 data-v-bdedfc22><span id="main-nav-aria-label" class="visually-hidden" data-v-bdedfc22>Main Navigation</span><!--[--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/blog/frontend/library.html" data-v-bdedfc22 data-v-95f5d58b data-v-30c06bd3><!--[-->博客<!--]--><!----></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/blog/summary/20210806.html" data-v-bdedfc22 data-v-95f5d58b data-v-30c06bd3><!--[-->日记周记<!--]--><!----></a><!--]--><!--[--><div class="VPFlyout VPNavBarMenuGroup active" data-v-bdedfc22 data-v-96001b6b><button type="button" class="button" aria-haspopup="true" aria-expanded="false" data-v-96001b6b><span class="text" data-v-96001b6b><!----> 读书笔记 <svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="text-icon" data-v-96001b6b><path d="M12,16c-0.3,0-0.5-0.1-0.7-0.3l-6-6c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l5.3,5.3l5.3-5.3c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-6,6C12.5,15.9,12.3,16,12,16z"></path></svg></span></button><div class="menu" data-v-96001b6b><div class="VPMenu" data-v-96001b6b data-v-e7ea1737><div class="items" data-v-e7ea1737><!--[--><!--[--><div class="VPMenuGroup" data-v-e7ea1737 data-v-b66affaf><p class="title" data-v-b66affaf>前端技术</p><!--[--><!--[--><div class="VPMenuLink" data-v-b66affaf data-v-a5bbb52c><a class="VPLink link" href="/blog/svg/rumen.html" data-v-a5bbb52c data-v-30c06bd3><!--[-->SVG基础入门<!--]--><!----></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-b66affaf data-v-a5bbb52c><a class="VPLink link" href="/blog/readingNotes/browserPrinciple.html" data-v-a5bbb52c data-v-30c06bd3><!--[-->浏览器工作原理与实践<!--]--><!----></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-b66affaf data-v-a5bbb52c><a class="VPLink link active" href="/blog/readingNotes/reactAndNodeJS.html" data-v-a5bbb52c data-v-30c06bd3><!--[-->react+Node.js开发实战<!--]--><!----></a></div><!--]--><!--[--><div class="VPMenuLink" data-v-b66affaf data-v-a5bbb52c><a class="VPLink link" href="/blog/readingNotes/jsgc4.html" data-v-a5bbb52c data-v-30c06bd3><!--[-->javascript高级程序设计第4版<!--]--><!----></a></div><!--]--><!--]--></div><!--]--><!--]--></div><!--[--><!--]--></div></div></div><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/blog/interviews/" data-v-bdedfc22 data-v-95f5d58b data-v-30c06bd3><!--[-->面试<!--]--><!----></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/photograph/home.html" data-v-bdedfc22 data-v-95f5d58b data-v-30c06bd3><!--[-->摄影<!--]--><!----></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/english/plan.html" data-v-bdedfc22 data-v-95f5d58b data-v-30c06bd3><!--[-->英语<!--]--><!----></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/wemedia/what.html" data-v-bdedfc22 data-v-95f5d58b data-v-30c06bd3><!--[-->自媒体<!--]--><!----></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/life/index.html" data-v-bdedfc22 data-v-95f5d58b data-v-30c06bd3><!--[-->生活<!--]--><!----></a><!--]--><!--[--><a class="VPLink link VPNavBarMenuLink" href="/about/me.html" data-v-bdedfc22 data-v-95f5d58b data-v-30c06bd3><!--[-->关于 <!--]--><!----></a><!--]--><!--]--></nav><!----><div class="VPNavBarAppearance appearance" data-v-be450ad9 data-v-da3f667a><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" aria-label="toggle dark mode" aria-checked="false" data-v-da3f667a data-v-0d529b6d data-v-f3c41672><span class="check" data-v-f3c41672><span class="icon" data-v-f3c41672><!--[--><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="sun" data-v-0d529b6d><path d="M12,18c-3.3,0-6-2.7-6-6s2.7-6,6-6s6,2.7,6,6S15.3,18,12,18zM12,8c-2.2,0-4,1.8-4,4c0,2.2,1.8,4,4,4c2.2,0,4-1.8,4-4C16,9.8,14.2,8,12,8z"></path><path d="M12,4c-0.6,0-1-0.4-1-1V1c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,3.6,12.6,4,12,4z"></path><path d="M12,24c-0.6,0-1-0.4-1-1v-2c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,23.6,12.6,24,12,24z"></path><path d="M5.6,6.6c-0.3,0-0.5-0.1-0.7-0.3L3.5,4.9c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C6.2,6.5,5.9,6.6,5.6,6.6z"></path><path d="M19.8,20.8c-0.3,0-0.5-0.1-0.7-0.3l-1.4-1.4c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C20.3,20.7,20,20.8,19.8,20.8z"></path><path d="M3,13H1c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S3.6,13,3,13z"></path><path d="M23,13h-2c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S23.6,13,23,13z"></path><path d="M4.2,20.8c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C4.7,20.7,4.5,20.8,4.2,20.8z"></path><path d="M18.4,6.6c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C18.9,6.5,18.6,6.6,18.4,6.6z"></path></svg><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="moon" data-v-0d529b6d><path d="M12.1,22c-0.3,0-0.6,0-0.9,0c-5.5-0.5-9.5-5.4-9-10.9c0.4-4.8,4.2-8.6,9-9c0.4,0,0.8,0.2,1,0.5c0.2,0.3,0.2,0.8-0.1,1.1c-2,2.7-1.4,6.4,1.3,8.4c2.1,1.6,5,1.6,7.1,0c0.3-0.2,0.7-0.3,1.1-0.1c0.3,0.2,0.5,0.6,0.5,1c-0.2,2.7-1.5,5.1-3.6,6.8C16.6,21.2,14.4,22,12.1,22zM9.3,4.4c-2.9,1-5,3.6-5.2,6.8c-0.4,4.4,2.8,8.3,7.2,8.7c2.1,0.2,4.2-0.4,5.8-1.8c1.1-0.9,1.9-2.1,2.4-3.4c-2.5,0.9-5.3,0.5-7.5-1.1C9.2,11.4,8.1,7.7,9.3,4.4z"></path></svg><!--]--></span></span></button></div><!----><div class="VPFlyout VPNavBarExtra extra" data-v-be450ad9 data-v-66bb1f24 data-v-96001b6b><button type="button" class="button" aria-haspopup="true" aria-expanded="false" aria-label="extra navigation" data-v-96001b6b><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="icon" data-v-96001b6b><circle cx="12" cy="12" r="2"></circle><circle cx="19" cy="12" r="2"></circle><circle cx="5" cy="12" r="2"></circle></svg></button><div class="menu" data-v-96001b6b><div class="VPMenu" data-v-96001b6b data-v-e7ea1737><!----><!--[--><!--[--><!----><div class="group" data-v-66bb1f24><div class="item appearance" data-v-66bb1f24><p class="label" data-v-66bb1f24>Appearance</p><div class="appearance-action" data-v-66bb1f24><button class="VPSwitch VPSwitchAppearance" type="button" role="switch" aria-label="toggle dark mode" aria-checked="false" data-v-66bb1f24 data-v-0d529b6d data-v-f3c41672><span class="check" data-v-f3c41672><span class="icon" data-v-f3c41672><!--[--><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="sun" data-v-0d529b6d><path d="M12,18c-3.3,0-6-2.7-6-6s2.7-6,6-6s6,2.7,6,6S15.3,18,12,18zM12,8c-2.2,0-4,1.8-4,4c0,2.2,1.8,4,4,4c2.2,0,4-1.8,4-4C16,9.8,14.2,8,12,8z"></path><path d="M12,4c-0.6,0-1-0.4-1-1V1c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,3.6,12.6,4,12,4z"></path><path d="M12,24c-0.6,0-1-0.4-1-1v-2c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,23.6,12.6,24,12,24z"></path><path d="M5.6,6.6c-0.3,0-0.5-0.1-0.7-0.3L3.5,4.9c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C6.2,6.5,5.9,6.6,5.6,6.6z"></path><path d="M19.8,20.8c-0.3,0-0.5-0.1-0.7-0.3l-1.4-1.4c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C20.3,20.7,20,20.8,19.8,20.8z"></path><path d="M3,13H1c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S3.6,13,3,13z"></path><path d="M23,13h-2c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S23.6,13,23,13z"></path><path d="M4.2,20.8c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C4.7,20.7,4.5,20.8,4.2,20.8z"></path><path d="M18.4,6.6c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C18.9,6.5,18.6,6.6,18.4,6.6z"></path></svg><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="moon" data-v-0d529b6d><path d="M12.1,22c-0.3,0-0.6,0-0.9,0c-5.5-0.5-9.5-5.4-9-10.9c0.4-4.8,4.2-8.6,9-9c0.4,0,0.8,0.2,1,0.5c0.2,0.3,0.2,0.8-0.1,1.1c-2,2.7-1.4,6.4,1.3,8.4c2.1,1.6,5,1.6,7.1,0c0.3-0.2,0.7-0.3,1.1-0.1c0.3,0.2,0.5,0.6,0.5,1c-0.2,2.7-1.5,5.1-3.6,6.8C16.6,21.2,14.4,22,12.1,22zM9.3,4.4c-2.9,1-5,3.6-5.2,6.8c-0.4,4.4,2.8,8.3,7.2,8.7c2.1,0.2,4.2-0.4,5.8-1.8c1.1-0.9,1.9-2.1,2.4-3.4c-2.5,0.9-5.3,0.5-7.5-1.1C9.2,11.4,8.1,7.7,9.3,4.4z"></path></svg><!--]--></span></span></button></div></div></div><!----><!--]--><!--]--></div></div></div><!--[--><!--]--><button type="button" class="VPNavBarHamburger hamburger" aria-label="mobile navigation" aria-expanded="false" aria-controls="VPNavScreen" data-v-be450ad9 data-v-e5dd9c1c><span class="container" data-v-e5dd9c1c><span class="top" data-v-e5dd9c1c></span><span class="middle" data-v-e5dd9c1c></span><span class="bottom" data-v-e5dd9c1c></span></span></button></div></div></div></div><!----></header><div class="VPLocalNav" data-v-93a960b4 data-v-2817d72e><button class="menu" aria-expanded="false" aria-controls="VPSidebarNav" data-v-2817d72e><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" viewbox="0 0 24 24" class="menu-icon" data-v-2817d72e><path d="M17,11H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h14c0.6,0,1,0.4,1,1S17.6,11,17,11z"></path><path d="M21,7H3C2.4,7,2,6.6,2,6s0.4-1,1-1h18c0.6,0,1,0.4,1,1S21.6,7,21,7z"></path><path d="M21,15H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h18c0.6,0,1,0.4,1,1S21.6,15,21,15z"></path><path d="M17,19H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h14c0.6,0,1,0.4,1,1S17.6,19,17,19z"></path></svg><span class="menu-text" data-v-2817d72e>菜单</span></button><a class="top-link" href="#" data-v-2817d72e>回到顶部</a></div><aside class="VPSidebar" data-v-93a960b4 data-v-c79ccefa><div class="curtain" data-v-c79ccefa></div><nav class="nav" id="VPSidebarNav" aria-labelledby="sidebar-aria-label" tabindex="-1" data-v-c79ccefa><span class="visually-hidden" id="sidebar-aria-label" data-v-c79ccefa> Sidebar Navigation </span><!--[--><!--]--><!--[--><div class="group" data-v-c79ccefa><div class="VPSidebarItem level-0" data-v-c79ccefa data-v-b05232f3><div class="item" role="button" data-v-b05232f3><div class="indicator" data-v-b05232f3></div><a class="VPLink link" data-v-b05232f3 data-v-30c06bd3><!--[--><p class="text" data-v-b05232f3>react+Node.js开发实战</p><!--]--><!----></a><!----></div><!----></div></div><!--]--><!--[--><!--]--></nav></aside><div class="VPContent has-sidebar" id="VPContent" data-v-93a960b4 data-v-0bd490fb><div class="VPDoc has-sidebar has-aside" data-v-0bd490fb data-v-c5936a1e><div class="container" data-v-c5936a1e><div class="aside" data-v-c5936a1e><div class="aside-curtain" data-v-c5936a1e></div><div class="aside-container" data-v-c5936a1e><div class="aside-content" data-v-c5936a1e><div class="VPDocAside" data-v-c5936a1e data-v-cdc66372><!--[--><!--]--><!--[--><!--]--><div class="VPDocAsideOutline" data-v-cdc66372 data-v-5dd9d5f6><div class="content" data-v-5dd9d5f6><div class="outline-marker" data-v-5dd9d5f6></div><div class="outline-title" data-v-5dd9d5f6>本页目录</div><nav aria-labelledby="doc-outline-aria-label" data-v-5dd9d5f6><span class="visually-hidden" id="doc-outline-aria-label" data-v-5dd9d5f6> Table of Contents for current page </span><ul class="root" data-v-5dd9d5f6 data-v-1188541a><!--[--><!--]--></ul></nav></div></div><!--[--><!--]--><div class="spacer" data-v-cdc66372></div><!--[--><!--]--><!----><!--[--><!--]--><!--[--><!--]--></div></div></div></div><div class="content" data-v-c5936a1e><div class="content-container" data-v-c5936a1e><!--[--><!--]--><main class="main" data-v-c5936a1e><div style="position:relative;" class="vp-doc _blog_readingNotes_reactAndNodeJS" data-v-c5936a1e><div><p>这篇文章记录《React+Node.js 开发实战，从入门到项目上线》书籍的笔记</p><p>这本书由袁林尹皓陈宁编著</p><h2 id="第-1-章-搭建-react-node-js-开发环境" tabindex="-1">第 1 章 搭建 React+Node.js 开发环境 <a class="header-anchor" href="#第-1-章-搭建-react-node-js-开发环境" aria-hidden="true">#</a></h2><h3 id="_1-2-2-npm、cnpm-和-nrm" tabindex="-1">1.2.2 npm、cnpm 和 nrm <a class="header-anchor" href="#_1-2-2-npm、cnpm-和-nrm" aria-hidden="true">#</a></h3><p>国内淘宝团队同步 NPM 实现的国内源</p><p>配置国内源</p><blockquote><p>npm --registry=<a href="https://registry.npm.taobao.org" target="_blank" rel="noreferrer">https://registry.npm.taobao.org</a></p></blockquote><p>安装 cnpm：</p><blockquote><p>npm install -g cnpm --registry=<a href="https://registry.npm.taobao.org" target="_blank" rel="noreferrer">https://registry.npm.taobao.org</a></p></blockquote><h3 id="_1-2-3-yarn" tabindex="-1">1.2.3 YARN <a class="header-anchor" href="#_1-2-3-yarn" aria-hidden="true">#</a></h3><p>facebook 开发的替代 NPM 的包管理工具，具有以下特性：</p><ul><li>速度快，缓存了曾经下过的包</li><li>安全，校验包完整性</li><li>可靠，使用详细简洁的 yarn.lock 锁文件</li></ul><p>更多请看文档：<a href="https://yarn.bootcss.com/" target="_blank" rel="noreferrer">yarn</a></p><h3 id="_1-2-4-npx-和-npm-scripts" tabindex="-1">1.2.4 npx 和 npm scripts <a class="header-anchor" href="#_1-2-4-npx-和-npm-scripts" aria-hidden="true">#</a></h3><p><code>npx</code> 是包执行器，安装 npm 时自带 npx，使用如下：</p><div class="language-shell"><button title="Copy Code" class="copy"></button><span class="lang">shell</span><pre class="shiki material-theme-palenight" tabindex="0"><code><span class="line"><span style="color:#676E95;font-style:italic;">#传统执行命令方式</span></span>
<span class="line"><span style="color:#82AAFF;">.</span><span style="color:#FFCB6B;">/node_modules/.bin/mocha</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">--version</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">#使用npx执行方式</span></span>
<span class="line"><span style="color:#FFCB6B;">npx</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">mocha</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">--version</span></span>
<span class="line"></span></code></pre></div><p><code>npm scripts</code>是在<code>package.json</code>文件中使用<code>scripts</code>字段定义的脚本命令</p><p>npm scripts 同时有两个钩子，pre 和 post，如下：</p><div class="language-js"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki material-theme-palenight" tabindex="0"><code><span class="line"><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#F07178;">  </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">scripts</span><span style="color:#89DDFF;">&quot;</span><span style="color:#F07178;">:</span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#89DDFF;">&quot;</span><span style="color:#F07178;">prestart</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">:</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">npm run build</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#89DDFF;">&quot;</span><span style="color:#F07178;">start</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">:</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">node ./bin/www</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#89DDFF;">&quot;</span><span style="color:#F07178;">poststart</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">:</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">echo node server started</span><span style="color:#89DDFF;">&quot;</span></span>
<span class="line"><span style="color:#F07178;">  </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span>
<span class="line"></span></code></pre></div><p>以上当执行<code>npm run start</code>时，会自动按照下面顺序执行：</p><blockquote><p>npm run prestart &amp; npm run start &amp; npm run poststart</p></blockquote><p>npm 默认还提供了其它很多钩子，如<code>prepublish,postpublish</code>和<code>pretest,posttest</code>等</p><h2 id="第-3-章-后端开发-node-js-技术从-0-到-1" tabindex="-1">第 3 章 后端开发：Node.js 技术从 0 到 1 <a class="header-anchor" href="#第-3-章-后端开发-node-js-技术从-0-到-1" aria-hidden="true">#</a></h2><h3 id="_3-3-3-调试工具" tabindex="-1">3.3.3 调试工具 <a class="header-anchor" href="#_3-3-3-调试工具" aria-hidden="true">#</a></h3><p>调试 Node.js 主要有以下两种方式：</p><ul><li>1.命令行调试</li><li>2.IDE 调试</li></ul><p><strong>命令行调试步骤</strong></p><div class="language-shell"><button title="Copy Code" class="copy"></button><span class="lang">shell</span><pre class="shiki material-theme-palenight" tabindex="0"><code><span class="line"><span style="color:#676E95;font-style:italic;"># 终端运行js文件，添加--inspect参数</span></span>
<span class="line"><span style="color:#FFCB6B;">node</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">--inspect</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">http.js</span></span>
<span class="line"></span>
<span class="line"><span style="color:#676E95;font-style:italic;"># 在edge或chrome浏览器地址栏输入以下命令</span></span>
<span class="line"><span style="color:#FFCB6B;">edge://inspect</span></span>
<span class="line"><span style="color:#FFCB6B;">chrome://inspect</span></span>
<span class="line"></span>
<span class="line"><span style="color:#676E95;font-style:italic;"># 在devices菜单页面中的Remote Target下，会列出可调试的服务，点击inspect</span></span>
<span class="line"></span>
<span class="line"><span style="color:#676E95;font-style:italic;"># 进入devTools页面，点击源，找到代码文件，如果没有，则添加对应文件</span></span>
<span class="line"></span>
<span class="line"><span style="color:#676E95;font-style:italic;"># 打上断点，访问服务，即可看到程序在断点处停止</span></span>
<span class="line"></span></code></pre></div><p><strong>IDE 调试</strong></p><ol><li><p>在 vscode 中找到想调试的 js 文件</p></li><li><p>点开左侧<code>debug/start debugging(运行和调试)</code>菜单，或者按快捷键 F5</p></li></ol><p>3.打上断点开始调试即可</p><p>ide 调试方式仍然是基于 node 命令实现的。</p><h3 id="_3-4-node-js-的常用模块" tabindex="-1">3.4 Node.js 的常用模块 <a class="header-anchor" href="#_3-4-node-js-的常用模块" aria-hidden="true">#</a></h3><h4 id="_3-4-1-全局变量" tabindex="-1">3.4.1 全局变量 <a class="header-anchor" href="#_3-4-1-全局变量" aria-hidden="true">#</a></h4><p>浏览器中 window 通常是全局变量，在 Node 中则是 global</p><p>全局对象 global 下通常有以下常用的变量：</p><ul><li><code>__filename</code>和<code>__dirname</code></li><li><code>setTimeout,setInterval,setImmediate</code>定时任务</li><li><code>console</code>控制台</li><li><code>process</code>进程</li></ul><h5 id="_1-filename-和-dirname" tabindex="-1">1.filename 和 dirname <a class="header-anchor" href="#_1-filename-和-dirname" aria-hidden="true">#</a></h5><p><code>__filename</code> 表示当前正在执行的脚本的文件名，是绝对路径</p><p><code>__dirname</code> 表示当前执行的脚本所在的目录，绝对路径</p><div class="language-shell"><button title="Copy Code" class="copy"></button><span class="lang">shell</span><pre class="shiki material-theme-palenight" tabindex="0"><code><span class="line"><span style="color:#676E95;font-style:italic;"># test_node.js</span></span>
<span class="line"><span style="color:#FFCB6B;">console.log(__filename</span><span style="color:#A6ACCD;">)</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">#E:\nuobel_blog\markdownFiles\blog\readingNotes\files\test_node.js</span></span>
<span class="line"><span style="color:#FFCB6B;">console.log(__dirname</span><span style="color:#A6ACCD;">)</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">#E:\nuobel_blog\markdownFiles\blog\readingNotes\files</span></span>
<span class="line"></span></code></pre></div><h5 id="_2-process-进程" tabindex="-1">2.process 进程 <a class="header-anchor" href="#_2-process-进程" aria-hidden="true">#</a></h5><p>process 进程代表当前 Node 进程状态的对象</p><p>通过 process 进程对象，可以使用比如<code>标准输出、获取Node版本、获取运行程序的操作系统、环境变量、进程生命周期</code>等功能</p><div class="language-js"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki material-theme-palenight" tabindex="0"><code><span class="line"><span style="color:#676E95;font-style:italic;">// 标准输出</span></span>
<span class="line"><span style="color:#A6ACCD;">process</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">stdout</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">write</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">hello world</span><span style="color:#A6ACCD;">\n</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;">)</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">// 环境变量</span></span>
<span class="line"><span style="color:#A6ACCD;">console</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">log</span><span style="color:#A6ACCD;">(process</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">env</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">PATH)</span></span>
<span class="line"></span></code></pre></div><h4 id="_3-4-2-工具模块" tabindex="-1">3.4.2 工具模块 <a class="header-anchor" href="#_3-4-2-工具模块" aria-hidden="true">#</a></h4><p>下面是一些常用实用的工具模块</p><h5 id="_1-os-模块" tabindex="-1">1.os 模块 <a class="header-anchor" href="#_1-os-模块" aria-hidden="true">#</a></h5><p>os 模块提供了一些基本的系统操作函数，通过 os 模块，Node 程序可以实现和操作系统的交互</p><div class="language-js"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki material-theme-palenight" tabindex="0"><code><span class="line"><span style="color:#C792EA;">const</span><span style="color:#A6ACCD;"> os </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">require</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">os</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;">)</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">// 系统内存总量</span></span>
<span class="line"><span style="color:#A6ACCD;">console</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">log</span><span style="color:#A6ACCD;">(os</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">totalmem</span><span style="color:#A6ACCD;">() </span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">1024</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">1024</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">MB</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;">)</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">// cpu核心数量</span></span>
<span class="line"><span style="color:#A6ACCD;">console</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">log</span><span style="color:#A6ACCD;">(os</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">cpus</span><span style="color:#A6ACCD;">()</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">length)</span></span>
<span class="line"></span></code></pre></div><h5 id="_2-path-模块" tabindex="-1">2.path 模块 <a class="header-anchor" href="#_2-path-模块" aria-hidden="true">#</a></h5><p>path 模块提供了和文件路径或目录路径相关处理的实用工具</p><div class="language-js"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki material-theme-palenight" tabindex="0"><code><span class="line"><span style="color:#C792EA;">const</span><span style="color:#A6ACCD;"> path </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">require</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">path</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;">)</span></span>
<span class="line"><span style="color:#C792EA;">const</span><span style="color:#A6ACCD;"> filepath </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">../app/http.js</span><span style="color:#89DDFF;">&quot;</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">// 返回路径中代表文件夹的部分</span></span>
<span class="line"><span style="color:#A6ACCD;">console</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">log</span><span style="color:#A6ACCD;">(path</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">dirname</span><span style="color:#A6ACCD;">(filepath)) </span><span style="color:#676E95;font-style:italic;">// ../app</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">// 连接生成规范的路径</span></span>
<span class="line"><span style="color:#A6ACCD;">console</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">log</span><span style="color:#A6ACCD;">(path</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">join</span><span style="color:#A6ACCD;">(__dirname</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> filepath))</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">// E:\markdownFiles\blog\readingNotes\app\http.js</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">// 文件后缀名</span></span>
<span class="line"><span style="color:#A6ACCD;">console</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">log</span><span style="color:#A6ACCD;">(path</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">extname</span><span style="color:#A6ACCD;">(filepath)) </span><span style="color:#676E95;font-style:italic;">// .js</span></span>
<span class="line"></span></code></pre></div><h5 id="_3-net-模块" tabindex="-1"><a href="http://3.net" target="_blank" rel="noreferrer">3.net</a> 模块 <a class="header-anchor" href="#_3-net-模块" aria-hidden="true">#</a></h5><p>net 模块提供了一些用于底层网络通信的工具，如 TCP server 的实现</p><h5 id="_4-其它" tabindex="-1">4.其它 <a class="header-anchor" href="#_4-其它" aria-hidden="true">#</a></h5><p>其它的如<code>fs,url</code>模块等也非常实用</p><p>fs 模块提供文件相关操作</p><p>url 模块提供 url 解析等操作</p><h4 id="_3-4-3-http-模块" tabindex="-1">3.4.3 HTTP 模块 <a class="header-anchor" href="#_3-4-3-http-模块" aria-hidden="true">#</a></h4><p>方便搭建 http 服务端和客户端</p><div class="language-js"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki material-theme-palenight" tabindex="0"><code><span class="line"><span style="color:#676E95;font-style:italic;">// http server</span></span>
<span class="line"><span style="color:#C792EA;">const</span><span style="color:#A6ACCD;"> http </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">require</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">http</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;">)</span></span>
<span class="line"><span style="color:#C792EA;">const</span><span style="color:#A6ACCD;"> url </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">require</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">url</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C792EA;">let</span><span style="color:#A6ACCD;"> server </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> http</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">createServer</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;font-style:italic;">req</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">res</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">=&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#89DDFF;">  </span><span style="color:#676E95;font-style:italic;">// 路由和查询串参数解析</span></span>
<span class="line"><span style="color:#F07178;">  </span><span style="color:#C792EA;">let</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">parsedUrl</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">=</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">url</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">parse</span><span style="color:#F07178;">(</span><span style="color:#A6ACCD;">req</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">url</span><span style="color:#89DDFF;">,</span><span style="color:#F07178;"> </span><span style="color:#FF9CAC;">true</span><span style="color:#F07178;">) </span><span style="color:#676E95;font-style:italic;">// 第二个参数为true表示将查询串转换为对象</span></span>
<span class="line"><span style="color:#F07178;">  </span><span style="color:#C792EA;">let</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">pathname</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">=</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">parsedUrl</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">pathname</span></span>
<span class="line"><span style="color:#F07178;">  </span><span style="color:#C792EA;">let</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">queryObj</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">=</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">parsedUrl</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">query</span></span>
<span class="line"><span style="color:#F07178;">  </span><span style="color:#A6ACCD;">console</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">log</span><span style="color:#F07178;">(</span><span style="color:#A6ACCD;">pathname</span><span style="color:#F07178;">) </span><span style="color:#676E95;font-style:italic;">// 路由</span></span>
<span class="line"><span style="color:#F07178;">  </span><span style="color:#A6ACCD;">console</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">log</span><span style="color:#F07178;">(</span><span style="color:#A6ACCD;">queryObj</span><span style="color:#F07178;">) </span><span style="color:#676E95;font-style:italic;">// 查询串对象</span></span>
<span class="line"><span style="color:#89DDFF;">  </span><span style="color:#676E95;font-style:italic;">// 接受body</span></span>
<span class="line"><span style="color:#F07178;">  </span><span style="color:#C792EA;">let</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">body</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">=</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">&quot;&quot;</span></span>
<span class="line"><span style="color:#F07178;">  </span><span style="color:#A6ACCD;">req</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">on</span><span style="color:#F07178;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">data</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;font-style:italic;">chunk</span><span style="color:#F07178;"> </span><span style="color:#C792EA;">=&gt;</span><span style="color:#F07178;"> (</span><span style="color:#A6ACCD;">body</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">+=</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">chunk</span><span style="color:#F07178;">))</span></span>
<span class="line"><span style="color:#F07178;">  </span><span style="color:#A6ACCD;">req</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">on</span><span style="color:#F07178;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">end</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">()</span><span style="color:#F07178;"> </span><span style="color:#C792EA;">=&gt;</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#89DDFF;font-style:italic;">if</span><span style="color:#F07178;"> (</span><span style="color:#A6ACCD;">req</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">headers</span><span style="color:#F07178;">[</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">content-type</span><span style="color:#89DDFF;">&quot;</span><span style="color:#F07178;">] </span><span style="color:#89DDFF;">===</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">application/json</span><span style="color:#89DDFF;">&quot;</span><span style="color:#F07178;">) </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#F07178;">      </span><span style="color:#A6ACCD;">console</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">log</span><span style="color:#F07178;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">body</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">body</span><span style="color:#F07178;">)</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#F07178;">  </span><span style="color:#89DDFF;">}</span><span style="color:#F07178;">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#89DDFF;">  </span><span style="color:#676E95;font-style:italic;">// 返回数据</span></span>
<span class="line"><span style="color:#F07178;">  </span><span style="color:#A6ACCD;">res</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">writeHead</span><span style="color:#F07178;">(</span><span style="color:#F78C6C;">200</span><span style="color:#89DDFF;">,</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">{</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#F07178;">content-type</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">:</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">text/plain</span><span style="color:#89DDFF;">&quot;</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">}</span><span style="color:#F07178;">)</span></span>
<span class="line"><span style="color:#F07178;">  </span><span style="color:#A6ACCD;">res</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">end</span><span style="color:#F07178;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">nihao</span><span style="color:#89DDFF;">&quot;</span><span style="color:#F07178;">)</span></span>
<span class="line"><span style="color:#89DDFF;">  </span><span style="color:#676E95;font-style:italic;">// 返回json字符串</span></span>
<span class="line"><span style="color:#89DDFF;">  </span><span style="color:#676E95;font-style:italic;">// res.writeHead(200, { &quot;content-type&quot;: &quot;application/json&quot; })</span></span>
<span class="line"><span style="color:#89DDFF;">  </span><span style="color:#676E95;font-style:italic;">// res.end(JSON.stringify({code:1}))</span></span>
<span class="line"><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">server</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">listen</span><span style="color:#A6ACCD;">(</span><span style="color:#F78C6C;">9000</span><span style="color:#A6ACCD;">)</span></span>
<span class="line"></span></code></pre></div><p>可通过 curl 命令请求</p><blockquote><p>curl localhost:9000?hi=123 -X POST -H &#39;Content-Type: application/json&#39; -d &#39;{&quot;name&quot;:&quot;huyu&quot;}&#39;</p></blockquote><p>服务端打印值如下：</p><div class="language-"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki material-theme-palenight" tabindex="0"><code><span class="line"><span style="color:#A6ACCD;">/</span></span>
<span class="line"><span style="color:#A6ACCD;">{hi:&#39;123&#39;}</span></span>
<span class="line"><span style="color:#A6ACCD;">body {&quot;name&quot;:&quot;huyu&quot;}</span></span>
<span class="line"><span style="color:#A6ACCD;"></span></span></code></pre></div><h4 id="_3-4-4-事件循环和-eventemitter" tabindex="-1">3.4.4 事件循环和 EventEmitter <a class="header-anchor" href="#_3-4-4-事件循环和-eventemitter" aria-hidden="true">#</a></h4><h5 id="_1-event-loop" tabindex="-1">1.Event Loop <a class="header-anchor" href="#_1-event-loop" aria-hidden="true">#</a></h5><p>事件循环即 Event Loop，是 Node 处理非阻塞 I/O 操作的机制</p><p>在启动时 Node 会初始化 Event Loop，每一个 EventLoop 都会有以下阶段</p><ul><li>timers：定时器，执行由<code>setTimeout,setInterval</code>调度的回调</li><li>pending callbacks：待定回调，系统操作的回调，如 tcp 错误类型</li><li>idle 空转，prepare：系统内部使用，先 idle 再 prepare</li><li>poll：轮询，处理轮询队列里的事件，在恰当的时候 Node 会被阻塞到这个阶段</li><li>check：检查，执行由<code>setImmediate</code>方法的回调</li><li>close callbacks：关闭回调，如<code>socket.on(&#39;close&#39;,...)</code></li></ul><div class="language-mermaid"><button title="Copy Code" class="copy"></button><span class="lang">mermaid</span><pre class="shiki material-theme-palenight" tabindex="0"><code><span class="line"><span style="color:#89DDFF;font-style:italic;">graph</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">TD</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">timers </span><span style="color:#89DDFF;font-style:italic;">--&gt;</span><span style="color:#A6ACCD;"> pc</span><span style="color:#89DDFF;font-style:italic;">[</span><span style="color:#C3E88D;">pending callbacks</span><span style="color:#89DDFF;font-style:italic;">]</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">--&gt;</span><span style="color:#A6ACCD;"> idle,prepare</span></span>
<span class="line"><span style="color:#A6ACCD;">idle,prepare </span><span style="color:#89DDFF;font-style:italic;">--&gt;</span><span style="color:#A6ACCD;"> poll </span><span style="color:#89DDFF;font-style:italic;">--&gt;</span><span style="color:#A6ACCD;"> check </span><span style="color:#89DDFF;font-style:italic;">--&gt;</span><span style="color:#A6ACCD;"> cc</span><span style="color:#89DDFF;font-style:italic;">[</span><span style="color:#C3E88D;">close callbacks</span><span style="color:#89DDFF;font-style:italic;">]</span></span>
<span class="line"><span style="color:#A6ACCD;">cc</span><span style="color:#89DDFF;font-style:italic;">[</span><span style="color:#C3E88D;">close callbacks</span><span style="color:#89DDFF;font-style:italic;">]</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;font-style:italic;">--</span><span style="color:#C3E88D;">loop </span><span style="color:#89DDFF;font-style:italic;">--&gt;</span><span style="color:#A6ACCD;"> timers</span></span>
<span class="line"><span style="color:#A6ACCD;">incoming:connections,data</span><span style="color:#89DDFF;font-style:italic;">--&gt;</span><span style="color:#A6ACCD;">poll</span></span>
<span class="line"></span>
<span class="line"></span></code></pre></div><p>从上面可以看出<code>setImmediate</code>和<code>setTimeout,setInterval</code>虽然功能类似，但调用时间是不同的。</p><p>在初始时，看进入 event loop 的快慢，可能先执行<code>setTimeout</code>然后是<code>setImmediate</code>，也可能先执行<code>setImmediate</code>然后是<code>setTimeout</code></p><p>但在轮询阶段，轮询后都是先执行<code>setImmediate</code>，再<code>setTimeout</code>的</p><h5 id="_2-eventemitter" tabindex="-1">2.EventEmitter <a class="header-anchor" href="#_2-eventemitter" aria-hidden="true">#</a></h5><p>Node 中事件分发对象，也是 events 模块提供的唯一对象</p><div class="language-js"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki material-theme-palenight" tabindex="0"><code><span class="line"><span style="color:#C792EA;">const</span><span style="color:#A6ACCD;"> events </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">require</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">events</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C792EA;">let</span><span style="color:#A6ACCD;"> emitter </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">new</span><span style="color:#A6ACCD;"> events</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">EventEmitter</span><span style="color:#A6ACCD;">()</span></span>
<span class="line"><span style="color:#A6ACCD;">emitter</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">on</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">someevent</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">function</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">(</span><span style="color:#A6ACCD;font-style:italic;">arg1</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">arg2</span><span style="color:#89DDFF;">)</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#F07178;">  </span><span style="color:#A6ACCD;">console</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">log</span><span style="color:#F07178;">(</span><span style="color:#A6ACCD;">arg1</span><span style="color:#89DDFF;">,</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">arg2</span><span style="color:#F07178;">) </span><span style="color:#676E95;font-style:italic;">// arg1 arg2</span></span>
<span class="line"><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;">)</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">emitter</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">emit</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">someevent</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">arg1</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">arg2</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;">)</span></span>
<span class="line"></span></code></pre></div><h2 id="第-4-章-构建与部署" tabindex="-1">第 4 章 构建与部署 <a class="header-anchor" href="#第-4-章-构建与部署" aria-hidden="true">#</a></h2><h3 id="_4-2-nginx-简介" tabindex="-1">4.2 Nginx 简介 <a class="header-anchor" href="#_4-2-nginx-简介" aria-hidden="true">#</a></h3><p>Nginx 是一款轻量级的 web 服务器、反向代理服务器</p><h4 id="_4-2-2-nginx-常用命令" tabindex="-1">4.2.2 nginx 常用命令 <a class="header-anchor" href="#_4-2-2-nginx-常用命令" aria-hidden="true">#</a></h4><ul><li><code>nginx -v</code>查看版本信息</li><li><code>nginx</code>启动 Nginx 服务</li><li><code>nginx -t</code>验证配置文件</li><li><code>nginx -s reload</code>重新加载配置文件</li><li><code>nginx -s stop</code>停止 nginx 服务</li></ul><h4 id="_4-2-3-nginx-配置" tabindex="-1">4.2.3 nginx 配置 <a class="header-anchor" href="#_4-2-3-nginx-配置" aria-hidden="true">#</a></h4><p>nginx 配置主要分成下面 4 部分：</p><ul><li>main：全局设置</li><li>server：主机设置，主要用于指定虚拟主机域名、IP 和端口</li><li>upstream：上游服务器设置，主要为反向代理、负载均衡相关配置</li><li>location：位置设置，用于匹配网页位置</li></ul><p>它们之间的关系如下：</p><ul><li>server 继承 main</li><li>location 继承 server</li><li>upstream 不继承也不会被继承，它有自己的特殊指令</li></ul><h5 id="_1-静态资源配置" tabindex="-1">1.静态资源配置 <a class="header-anchor" href="#_1-静态资源配置" aria-hidden="true">#</a></h5><p>root 指向资源目录</p><p>这里说下 root 和 alias 的区别：</p><ul><li>root：处理后相当于 <code>root 路径+location 路径</code></li><li>alias：直接用 alias 路径替换 location 路径</li></ul><div class="language-nginx"><button title="Copy Code" class="copy"></button><span class="lang">nginx</span><pre class="shiki material-theme-palenight" tabindex="0"><code><span class="line"><span style="color:#A6ACCD;">server</span><span style="color:#F07178;">{</span></span>
<span class="line"><span style="color:#F07178;"> </span><span style="color:#89DDFF;"> listen </span><span style="color:#F07178;">80</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F07178;"> </span><span style="color:#89DDFF;"> server_name </span><span style="color:#F07178;">site.com</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F07178;">  </span><span style="color:#C792EA;">location</span><span style="color:#F07178;"> </span><span style="color:#FFCB6B;">/ </span><span style="color:#F07178;">{</span></span>
<span class="line"><span style="color:#F07178;">   </span><span style="color:#89DDFF;"> root </span><span style="color:#F07178;">/var/www</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F07178;">   </span><span style="color:#89DDFF;"> index </span><span style="color:#F07178;">index.html</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F07178;">  }</span></span>
<span class="line"><span style="color:#F07178;">}</span></span>
<span class="line"></span></code></pre></div><h5 id="_2-反向代理配置" tabindex="-1">2.反向代理配置 <a class="header-anchor" href="#_2-反向代理配置" aria-hidden="true">#</a></h5><p>通过 proxy_pass 指向对应服务地址</p><div class="language-nginx"><button title="Copy Code" class="copy"></button><span class="lang">nginx</span><pre class="shiki material-theme-palenight" tabindex="0"><code><span class="line"><span style="color:#A6ACCD;">server</span><span style="color:#F07178;">{</span></span>
<span class="line"><span style="color:#F07178;"> </span><span style="color:#89DDFF;"> listen </span><span style="color:#F07178;">80</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F07178;"> </span><span style="color:#89DDFF;"> server_name </span><span style="color:#F07178;">api.com</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F07178;">  </span><span style="color:#C792EA;">location</span><span style="color:#F07178;"> </span><span style="color:#FFCB6B;">/ </span><span style="color:#F07178;">{</span></span>
<span class="line"><span style="color:#F07178;">   </span><span style="color:#89DDFF;"> proxy_pass </span><span style="color:#F07178;">http://127.0.0.1:8080</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#676E95;font-style:italic;"># 客户端ip转发</span></span>
<span class="line"><span style="color:#F07178;">   </span><span style="color:#89DDFF;"> proxy_set_header </span><span style="color:#F07178;">X-Real-Ip </span><span style="color:#89DDFF;">$</span><span style="color:#A6ACCD;">remote_addr</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#676E95;font-style:italic;">#The $proxy_add_x_forwarded_for is equal to $http_x_forwarded_for,$remote_addr</span></span>
<span class="line"><span style="color:#F07178;">   </span><span style="color:#89DDFF;"> proxy_set_header </span><span style="color:#F07178;">X-Forwarded-For </span><span style="color:#89DDFF;">$</span><span style="color:#A6ACCD;">proxy_add_x_forwarded_for</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F07178;">  }</span></span>
<span class="line"><span style="color:#F07178;">}</span></span>
<span class="line"></span></code></pre></div><h5 id="_3-负载均衡配置" tabindex="-1">3.负载均衡配置 <a class="header-anchor" href="#_3-负载均衡配置" aria-hidden="true">#</a></h5><p>upstream 命令实现负载均衡，weight 表示权重</p><div class="language-nginx"><button title="Copy Code" class="copy"></button><span class="lang">nginx</span><pre class="shiki material-theme-palenight" tabindex="0"><code><span class="line"><span style="color:#C792EA;">upstream</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">api.com</span><span style="color:#A6ACCD;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">server</span><span style="color:#A6ACCD;"> 127.0.0.1:8080 </span><span style="color:#A6ACCD;font-style:italic;">weight</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">  server 127.0.0.1:8081 </span><span style="color:#A6ACCD;font-style:italic;">weight</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">server{</span></span>
<span class="line"><span style="color:#A6ACCD;">  listen </span><span style="color:#F78C6C;">80</span><span style="color:#A6ACCD;">;</span></span>
<span class="line"><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;"> server_name </span><span style="color:#A6ACCD;">api.com</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#C792EA;">location</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">/ </span><span style="color:#A6ACCD;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#676E95;font-style:italic;">#值和upstream的值需要对应上</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;"> proxy_pass </span><span style="color:#A6ACCD;">http://api.com</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">  }</span></span>
<span class="line"></span></code></pre></div><h4 id="_4-2-4-nginx-高级特性" tabindex="-1">4.2.4 nginx 高级特性 <a class="header-anchor" href="#_4-2-4-nginx-高级特性" aria-hidden="true">#</a></h4><h5 id="_1-缓存控制" tabindex="-1">1.缓存控制 <a class="header-anchor" href="#_1-缓存控制" aria-hidden="true">#</a></h5><p>通过 add_header 添加缓存 http 返回头信息</p><p>下面是增加<code>cache-control</code>头示例</p><div class="language-nginx"><button title="Copy Code" class="copy"></button><span class="lang">nginx</span><pre class="shiki material-theme-palenight" tabindex="0"><code><span class="line"><span style="color:#A6ACCD;">server</span><span style="color:#F07178;">{</span></span>
<span class="line"><span style="color:#F07178;"> </span><span style="color:#89DDFF;"> listen </span><span style="color:#F07178;">80</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F07178;"> </span><span style="color:#89DDFF;"> server_name </span><span style="color:#F07178;">site.com</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#F07178;">  </span><span style="color:#C792EA;">location</span><span style="color:#F07178;"> </span><span style="color:#FFCB6B;">/img/ </span><span style="color:#F07178;">{</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#676E95;font-style:italic;"># 缓存10秒</span></span>
<span class="line"><span style="color:#F07178;">   </span><span style="color:#89DDFF;"> add_header </span><span style="color:#F07178;">Cache-Control max-age=10</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F07178;">   </span><span style="color:#89DDFF;"> alias </span><span style="color:#F07178;">/var/www/static/img/</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F07178;">  }</span></span>
<span class="line"><span style="color:#F07178;">}</span></span>
<span class="line"></span></code></pre></div><h5 id="_2-cors-跨域" tabindex="-1">2.CORS 跨域 <a class="header-anchor" href="#_2-cors-跨域" aria-hidden="true">#</a></h5><p>如下，增加一些 cors 头，让服务端支持特定域名跨域访问</p><div class="language-nginx"><button title="Copy Code" class="copy"></button><span class="lang">nginx</span><pre class="shiki material-theme-palenight" tabindex="0"><code><span class="line"><span style="color:#C792EA;">upstream</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">api.com</span><span style="color:#A6ACCD;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">server</span><span style="color:#A6ACCD;"> 127.0.0.1:8080 </span><span style="color:#A6ACCD;font-style:italic;">weight</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">  server 127.0.0.1:8081 </span><span style="color:#A6ACCD;font-style:italic;">weight</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">1</span><span style="color:#A6ACCD;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">server{</span></span>
<span class="line"><span style="color:#A6ACCD;">  listen </span><span style="color:#F78C6C;">80</span><span style="color:#A6ACCD;">;</span></span>
<span class="line"><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;"> server_name </span><span style="color:#A6ACCD;">api.com</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#C792EA;">location</span><span style="color:#A6ACCD;"> </span><span style="color:#FFCB6B;">/ </span><span style="color:#A6ACCD;">{</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#676E95;font-style:italic;">#设置初始值</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;"> set $</span><span style="color:#A6ACCD;">cors </span><span style="color:#C3E88D;">&quot;&quot;</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#676E95;font-style:italic;">#只允许domain1.com和domain2.com跨域访问api.com</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;font-style:italic;">if</span><span style="color:#A6ACCD;"> (</span><span style="color:#89DDFF;">$</span><span style="color:#A6ACCD;">http_origin </span><span style="color:#89DDFF;">~* </span><span style="color:#A6ACCD;">(domain1.com|domain2.com)) </span><span style="color:#F07178;">{</span></span>
<span class="line"><span style="color:#F07178;">     </span><span style="color:#89DDFF;"> set $</span><span style="color:#A6ACCD;">cors</span><span style="color:#F07178;"> </span><span style="color:#C3E88D;">&quot;true&quot;</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F07178;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#676E95;font-style:italic;"># 判断逻辑</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;font-style:italic;">if</span><span style="color:#A6ACCD;"> (</span><span style="color:#89DDFF;">$</span><span style="color:#A6ACCD;">cors </span><span style="color:#89DDFF;">= </span><span style="color:#C3E88D;">&#39;true&#39;</span><span style="color:#A6ACCD;">) </span><span style="color:#F07178;">{</span></span>
<span class="line"><span style="color:#F07178;">      </span><span style="color:#676E95;font-style:italic;">#允许该源跨域请求</span></span>
<span class="line"><span style="color:#F07178;">     </span><span style="color:#89DDFF;"> add_header </span><span style="color:#C3E88D;">&#39;Access-Control-Allow-Origin&#39;</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">$</span><span style="color:#A6ACCD;">http_origin</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F07178;">      </span><span style="color:#676E95;font-style:italic;">#允许携带cookie</span></span>
<span class="line"><span style="color:#F07178;">     </span><span style="color:#89DDFF;"> add_header </span><span style="color:#C3E88D;">&#39;Access-Control-Allow-Credentials&#39;</span><span style="color:#F07178;"> </span><span style="color:#C3E88D;">&#39;true&#39;</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F07178;">      </span><span style="color:#676E95;font-style:italic;">#允许跨域请求的方法</span></span>
<span class="line"><span style="color:#F07178;">     </span><span style="color:#89DDFF;"> add_header </span><span style="color:#C3E88D;">&#39;Access-Control-Allow-Methods&#39;</span><span style="color:#F07178;"> </span><span style="color:#C3E88D;">&#39;GET,POST&#39;</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F07178;">      </span><span style="color:#676E95;font-style:italic;">#允许修改的头部</span></span>
<span class="line"><span style="color:#F07178;">     </span><span style="color:#89DDFF;"> add_header </span><span style="color:#C3E88D;">&#39;Access-Control-Allow-Headers&#39;</span><span style="color:#F07178;"> </span><span style="color:#C3E88D;">&#39;Content-Type&#39;</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#F07178;">    }</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;"> proxy_pass </span><span style="color:#A6ACCD;">http://api.com</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">  }</span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span>
<span class="line"></span></code></pre></div><h5 id="_3-https-配置" tabindex="-1">3.https 配置 <a class="header-anchor" href="#_3-https-配置" aria-hidden="true">#</a></h5><p>监听 443 端口并配置密钥</p><div class="language-nginx"><button title="Copy Code" class="copy"></button><span class="lang">nginx</span><pre class="shiki material-theme-palenight" tabindex="0"><code><span class="line"><span style="color:#C792EA;">server</span><span style="color:#A6ACCD;"> {</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;"> listen </span><span style="color:#A6ACCD;">      </span><span style="color:#F78C6C;">443</span><span style="color:#A6ACCD;"> ssl http2</span><span style="color:#89DDFF;"> default_server;</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;"> listen </span><span style="color:#A6ACCD;">      [::]:443 ssl http2</span><span style="color:#89DDFF;"> default_server;</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;"> server_name </span><span style="color:#A6ACCD;"> www.nuobel.com</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;"> root </span><span style="color:#A6ACCD;">        /usr/share/nginx/html</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#676E95;font-style:italic;">#ssl证书公钥，这里放在/etc/nginx/ssl目录下</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;"> ssl_certificate </span><span style="color:#A6ACCD;">ssl/1_www.nuobel.com_bundle.crt</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#676E95;font-style:italic;">#ssl证书私钥</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;"> ssl_certificate_key </span><span style="color:#A6ACCD;">ssl/2_www.nuobel.com.key</span><span style="color:#89DDFF;">;</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;"> ssl_session_cache </span><span style="color:#A6ACCD;">shared:SSL:1m</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;"> ssl_session_timeout </span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">10m</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;"> ssl_ciphers </span><span style="color:#A6ACCD;">HIGH:!aNULL:!MD5</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">   </span><span style="color:#89DDFF;"> ssl_prefer_server_ciphers </span><span style="color:#A6ACCD;">on</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#A6ACCD;">}</span></span>
<span class="line"></span></code></pre></div><h3 id="_4-3-pm2-简介" tabindex="-1">4.3 PM2 简介 <a class="header-anchor" href="#_4-3-pm2-简介" aria-hidden="true">#</a></h3><p>PM2 是专门针对 Node 的一款功能强大的进程管理工具</p><p>主流进程管理工具有以下 4 种：</p><ul><li>Supervisor</li><li>Forever</li><li>Nodemon</li><li>PM2</li></ul><p>其中 PM2 功能最强大</p><h4 id="_4-3-3-pm2-的安装和使用" tabindex="-1">4.3.3 PM2 的安装和使用 <a class="header-anchor" href="#_4-3-3-pm2-的安装和使用" aria-hidden="true">#</a></h4><p>安装</p><blockquote><p>npm install -g pm2</p></blockquote><blockquote><p>pm2 -v</p></blockquote><p>使用</p><div class="language-shell"><button title="Copy Code" class="copy"></button><span class="lang">shell</span><pre class="shiki material-theme-palenight" tabindex="0"><code><span class="line"><span style="color:#676E95;font-style:italic;">#启动node程序</span></span>
<span class="line"><span style="color:#FFCB6B;">pm2</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">start</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">app.js</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">#查看应用详细信息,0为应用id，app为应用名称</span></span>
<span class="line"><span style="color:#FFCB6B;">pm2</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">show</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">0</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">或pm2</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">show</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">app</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">#查看应用环境变量</span></span>
<span class="line"><span style="color:#FFCB6B;">pm2</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">env</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">0</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">#监控</span></span>
<span class="line"><span style="color:#FFCB6B;">pm2</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">monit</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">#查看应用运行状态</span></span>
<span class="line"><span style="color:#FFCB6B;">pm2</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">list</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">#保存所有的pm2应用</span></span>
<span class="line"><span style="color:#FFCB6B;">pm2</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">save</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">#打开pm2开机启动钩子</span></span>
<span class="line"><span style="color:#FFCB6B;">pm2</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">startup</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">#开机启动钩子可能会提示要执行一个命令，执行就是了。</span></span>
<span class="line"><span style="color:#FFCB6B;">sudo</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">env</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">PATH=</span><span style="color:#A6ACCD;">$PATH</span><span style="color:#C3E88D;">:/xx/xxlaunched</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">-u</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">xx</span></span>
<span class="line"></span></code></pre></div><h2 id="第-7-章-node-js-进阶" tabindex="-1">第 7 章 Node.js 进阶 <a class="header-anchor" href="#第-7-章-node-js-进阶" aria-hidden="true">#</a></h2><p>常见的服务端开发主要解决下面几个问题，就能够满足多数场景的后端开发需求</p><h3 id="_7-1-跨域" tabindex="-1">7.1 跨域 <a class="header-anchor" href="#_7-1-跨域" aria-hidden="true">#</a></h3><h4 id="_7-1-1-同源策略" tabindex="-1">7.1.1 同源策略 <a class="header-anchor" href="#_7-1-1-同源策略" aria-hidden="true">#</a></h4><p>同源策略（same origin policy）由网景提出的一个安全策略，它限制了从同一个源加载的脚本或文档和另外一个源的资源进行交互。</p><p>同源策略是浏览器最核心和最基本的安全功能，web 是建立在同源策略的基础之上。</p><p>什么是同源：两个页面的<strong>协议、主机和端口</strong>都相同</p><h4 id="_7-1-2-跨域方案" tabindex="-1">7.1.2 跨域方案 <a class="header-anchor" href="#_7-1-2-跨域方案" aria-hidden="true">#</a></h4><p>主要由以下几种：</p><ul><li><p>1.jsonp 方案</p><p>利用<code>script,img,link,iframe</code>标签允许跨域的特点，要回传数据则使用的是<code>script</code>标签。</p></li><li><p>2.CORS 方案</p><p>CORS（cross-origin resource sharing）跨域资源共享，使用<strong>额外的 HTTP 头</strong>来告诉浏览器是否允许不同源的资源请求</p><ul><li><code>Access-Control-Allow-Origin</code>：允许的请求源</li><li><code>Access-Control-Allow-Methods</code>：允许的 http 方法</li><li><code>Access-Control-Allow-Headers</code>：允许的 http 请求头信息</li></ul><p>当 CORS 跨域请求发生时，浏览器会发出一个 Options 的<strong>预检</strong>（preflight）请求</p></li><li><p>3.Proxy 方案</p></li></ul><p>原理是绕过浏览器跨域限制，通过一个同源的 web 服务代理来转发请求到目标服务上。</p><h3 id="_7-2-鉴权" tabindex="-1">7.2 鉴权 <a class="header-anchor" href="#_7-2-鉴权" aria-hidden="true">#</a></h3><p>对接口访问进行账号认证，即用户鉴权</p><p>主要有两种方式，原理都类似</p><h4 id="_7-2-1-基于-session" tabindex="-1">7.2.1 基于 session <a class="header-anchor" href="#_7-2-1-基于-session" aria-hidden="true">#</a></h4><p>session 原理大概如下：</p><ul><li>1.用户访问并登录服务器，服务器把用户状态以某种形式记录，记录内容就是 session</li><li>2.同时客户端会保存一个凭据，凭据一般存储在浏览器 cookie 中</li><li>3.当客户端再次访问时，携带之前的凭据（cookie），服务端根据凭据查找保存的用户状态信息</li></ul><p>nodejs 中如果使用的是 express，可以使用<code>express-session</code>库</p><h4 id="_7-2-2-基于-jwt" tabindex="-1">7.2.2 基于 JWT <a class="header-anchor" href="#_7-2-2-基于-jwt" aria-hidden="true">#</a></h4><p>JWT 即 JSON web token，jwt 通常由三部分组成：消息体（payload）、头信息（header）和签名（signature），由<code>.</code>连接</p><p>nodejs 可以使用<code>jsonwebtoken</code>库</p><p>格式如下：</p><div class="language-js"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki material-theme-palenight" tabindex="0"><code><span class="line"><span style="color:#C792EA;">let</span><span style="color:#A6ACCD;"> jwt </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">require</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">jsonwebtoken</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;">)</span></span>
<span class="line"><span style="color:#C792EA;">let</span><span style="color:#A6ACCD;"> palyloadString </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> JSON</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">stringify</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">{</span><span style="color:#A6ACCD;"> </span><span style="color:#F07178;">name</span><span style="color:#89DDFF;">:</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">huyu</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#F07178;">exp</span><span style="color:#89DDFF;">:</span><span style="color:#A6ACCD;"> Date</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">now</span><span style="color:#A6ACCD;">() </span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;"> </span><span style="color:#F78C6C;">10000</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;">)</span></span>
<span class="line"><span style="color:#C792EA;">let</span><span style="color:#A6ACCD;"> headerString </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> JSON</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">stringify</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">{</span><span style="color:#A6ACCD;"> </span><span style="color:#F07178;">typ</span><span style="color:#89DDFF;">:</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">JWT</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> </span><span style="color:#F07178;">alg</span><span style="color:#89DDFF;">:</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">HS256</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;">)</span></span>
<span class="line"><span style="color:#C792EA;">let</span><span style="color:#A6ACCD;"> secret </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">iamsecret</span><span style="color:#89DDFF;">&quot;</span></span>
<span class="line"><span style="color:#C792EA;">let</span><span style="color:#A6ACCD;"> payload </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> window</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">btoa</span><span style="color:#A6ACCD;">(palyloadString)</span></span>
<span class="line"><span style="color:#C792EA;">let</span><span style="color:#A6ACCD;"> header </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> window</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">btoa</span><span style="color:#A6ACCD;">(headerString)</span></span>
<span class="line"><span style="color:#C792EA;">let</span><span style="color:#A6ACCD;"> token </span><span style="color:#89DDFF;">=</span></span>
<span class="line"><span style="color:#A6ACCD;">  payload </span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">.</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;"> header </span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">.</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;"> jwt</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">sign</span><span style="color:#A6ACCD;">(header </span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">.</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">+</span><span style="color:#A6ACCD;"> payload</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;"> secret)</span></span>
<span class="line"></span></code></pre></div><p>很明显，从 token 值中，解析后可以直接拿到内容</p><h4 id="_7-2-3-各自优缺点" tabindex="-1">7.2.3 各自优缺点 <a class="header-anchor" href="#_7-2-3-各自优缺点" aria-hidden="true">#</a></h4><p>jwt 相比 session 优势：</p><ul><li>jwt 存储在客户端，减少服务器 session 压力</li><li>jwt 存储在客户端，更易于水平扩展，从而实现单点登录</li></ul><p>jwt 不足：</p><ul><li>jwt 信息只经过 base64 编码，不会被加密，只是经过签名防止篡改。因此必须使用 https 来加密通信，否则容易泄露</li><li>jwt 存储在客户端，信息中包含有效期，服务端不好控制过期</li></ul><p>jwt 主要用途：</p><ul><li>用于生成 token，例如预防 csrf 攻击的 token，以及鉴权授权等</li><li>其它签名场景</li></ul><h3 id="_7-3-缓存" tabindex="-1">7.3 缓存 <a class="header-anchor" href="#_7-3-缓存" aria-hidden="true">#</a></h3><p>缓存已经成为高并发、高性能架构的关键组件，缓存的应用场景主要包括：</p><ul><li>有高性能需求，如秒杀、解决一些文件或数据库查询较慢的问题</li><li>缓解数据库压力</li><li>无数据实时性要求，经常读取但很少改变的数据，如配置项等</li></ul><p>主流的缓存解决方案一般有 <strong>Redis 和 Memcached</strong>，无论是数据类型还是持久化和集群管理，Redis 都比 Memcached 更加强大。</p><h3 id="_7-4-对象关系映射" tabindex="-1">7.4 对象关系映射 <a class="header-anchor" href="#_7-4-对象关系映射" aria-hidden="true">#</a></h3><p>在后端实际开发中，数据库是绕不开的，为了抹平数据库之间的操作差异，便有了对象关系映射（Object/Relation Mapping,ORM）</p><p>有了 ORM，让我们对数据库的操作可以像操作对象那样方便快捷</p><h4 id="_7-4-1-sequelize-关系型数据库的-orm-实现" tabindex="-1">7.4.1 Sequelize-关系型数据库的 ORM 实现 <a class="header-anchor" href="#_7-4-1-sequelize-关系型数据库的-orm-实现" aria-hidden="true">#</a></h4><p>Node 有如下几中常见的 ORM 框架：</p><ul><li>Sequelize，老牌的 ORM 框架，支持多种关系型数据库，文档完善，更新频繁</li><li>BookShelf，没有 sequelize 使用广泛</li><li>TypeORM，对 typescript 支持较好</li></ul><p>Sequelize 是一个基于 Promise 的 Node ORM 框架，支持 PostgreSQL、mysql、MariaDB、SQLite 以及 Microsoft SQL server</p><p>官网：<a href="https://sequelize.org" target="_blank" rel="noreferrer">https://sequelize.org</a></p><h4 id="_7-4-2-mongoose-mongodb-的-orm-实现" tabindex="-1">7.4.2 Mongoose-MongoDB 的 ORM 实现 <a class="header-anchor" href="#_7-4-2-mongoose-mongodb-的-orm-实现" aria-hidden="true">#</a></h4><p>对非关系型数据库 MongoDB 的操作，常见的就是 Mongoose 了，官网：<a href="https://mongoosejs.com" target="_blank" rel="noreferrer">https://mongoosejs.com</a></p><h2 id="第-9-章-项目优化" tabindex="-1">第 9 章 项目优化 <a class="header-anchor" href="#第-9-章-项目优化" aria-hidden="true">#</a></h2><p>项目优化主要有以下几个点：</p><ul><li>浏览器缓存：减少服务器压力，提升用户体验</li><li>压缩：前端压缩（uglify）和服务端压缩（gzip）</li><li>懒加载：延时加载，真正使用时再去加载对应的模块</li><li>按需引入：只引入真正需要并使用的代码或者模块</li><li>负载均衡：水平扩展服务端的性能，同时保证高可用</li><li>CDN：内容分发网络</li></ul><h3 id="_9-1-浏览器缓存" tabindex="-1">9.1 浏览器缓存 <a class="header-anchor" href="#_9-1-浏览器缓存" aria-hidden="true">#</a></h3><p>浏览器缓存简单来说就是在本地保存请求资源的副本，再根据条件看是否取用副本</p><h4 id="_9-1-1-强缓存" tabindex="-1">9.1.1 强缓存 <a class="header-anchor" href="#_9-1-1-强缓存" aria-hidden="true">#</a></h4><p>强缓存不需要发送网络请求，强制使用本地缓存，可通过如下响应头设置：</p><ul><li><code>Expires:Tue,28 Jan 2021 12:59:59 GMT</code>：直接指定过期时间，http1.0 添加</li><li><code>Cache-Control:max-age=3600</code>：相对于请求时间的有效时间（单位秒），http1.1 添加</li></ul><p>有以下几个注意的点：</p><ul><li>使用<code>Expires</code>时，如果服务器和客户端时间不一致，则过期时间就不准确</li><li>如果<code>expires</code>和<code>Cache-Control</code>同时设置，则<code>expires</code>会被忽略</li><li>如果资源过期，则进入下一步--协商缓存</li></ul><p>另外<code>Cache-Control</code>还有其它几个值：</p><ul><li>private:只能被私有缓存（如浏览器），不能作为共享缓存（如代理服务器不能缓存）</li><li>public：可以被任何对象缓存</li><li>no-cache：需要和服务器进行缓存验证，这是协商缓存的配置之一</li><li>no-store：不使用任何缓存</li></ul><h4 id="_9-1-2-协商缓存" tabindex="-1">9.1.2 协商缓存 <a class="header-anchor" href="#_9-1-2-协商缓存" aria-hidden="true">#</a></h4><p>协商缓存，需要服务器校验是否使用缓存，如果缓存过期，则返回最新资源，未过期，则返回 304 状态码</p><p>主要请求和响应头如下：</p><ul><li><code>if-modified-since和last-modified</code>：进行修改时间的比较</li><li><code>if-none-match和ETag</code>：进行文件唯一标识的比较，文件唯一标识一般是内容散列值、最后修改时间的 hash 值或最简单的版本号</li></ul><p>当<code>Last-Modified</code>和<code>ETag</code>同时存在时，浏览器优先考虑 ETag</p><p>他们主要差异如下：</p><ul><li>精度方面：Etag 比 last-modified 高，后者只能精确到秒级修改</li><li>性能方面：last-modified 比 etag 好，etag 比较耗资源</li></ul><h4 id="_9-1-3-缓存位置" tabindex="-1">9.1.3 缓存位置 <a class="header-anchor" href="#_9-1-3-缓存位置" aria-hidden="true">#</a></h4><p>如果浏览器确定使用缓存，那么这个缓存是存储在哪里的呢？</p><p>缓存位置主要有以下 4 种：</p><ul><li>Service Worker Cache</li><li>Memory Cache</li><li>Disk Cache</li><li>Push Cache</li></ul><p>上面四种缓存优先级从上到下由高到低</p><h5 id="_1-service-worker-cache" tabindex="-1">1.Service Worker Cache <a class="header-anchor" href="#_1-service-worker-cache" aria-hidden="true">#</a></h5><p><code>Service Worker</code>作为独立于当前页面的一段运行在浏览器后台的线程，离线缓存使它的功能之一，这个离线缓存就是说的<code>Service Worker Cache</code></p><p><code>Service Worker</code>也是 PWA 实现的重要基础</p><h5 id="_2-memory-cache-和-disk-cache" tabindex="-1">2.Memory Cache 和 Disk Cache <a class="header-anchor" href="#_2-memory-cache-和-disk-cache" aria-hidden="true">#</a></h5><p>内存缓存和磁盘缓存，这两个在平时很常见，大部分缓存都是基于这两个</p><p>浏览器怎么决定资源应该存储在哪里呢？</p><ul><li>浏览器会优先存储到内存，比较大的文件放磁盘</li><li>系统内存使用率过高时，会存储到磁盘</li></ul><p>这两者有什么区别？</p><ul><li>存储类型：内存一般存储页面已加载的样式、脚本和图片等，磁盘则什么资源都可以存。</li><li>读取速度：内存大于磁盘</li><li>存储容量：磁盘大于内存</li><li>存储时间：磁盘大于内存。关闭 tab 页，内存中对应的缓存资源会被浏览器释放</li></ul><h5 id="_3-push-cache" tabindex="-1">3.Push Cache <a class="header-anchor" href="#_3-push-cache" aria-hidden="true">#</a></h5><p>推送缓存，是 HTTP2 中的东西，只在会话（session）中存在，会话结束就会被释放。</p><p>用的很少。</p><h4 id="_9-1-4-缓存策略" tabindex="-1">9.1.4 缓存策略 <a class="header-anchor" href="#_9-1-4-缓存策略" aria-hidden="true">#</a></h4><p>知道了浏览器缓存原理，在平时我们该如何使用？</p><ul><li>1.首先对于不变化或不常变化的资源，设置一个比较长的有效期，如一年后过期:<code>Cache-Control:max-age=31536000</code></li><li>2.经常发生变化的资源：使用协商缓存，显著减少响应数据的大小：<code>Cache-Control:no-cache</code>+<code>ETag或Last-Modified</code></li></ul><blockquote><p><strong>注意的问题</strong>：如果设置了一个超长的有效期，中间文件修改了怎么办？此时可以在打包资源时，针对资源内容计算一个 HASH 值，将 hash 值添加到文件名中，这样文件修改 hash 值也会变化。 webpack 就提供了文件名 hash 功能。</p></blockquote><h3 id="_9-2-压缩" tabindex="-1">9.2 压缩 <a class="header-anchor" href="#_9-2-压缩" aria-hidden="true">#</a></h3><p>压缩可以减小资源体检大小，这里主要分前端压缩和服务端压缩</p><h4 id="_9-2-1-前端压缩" tabindex="-1">9.2.1 前端压缩 <a class="header-anchor" href="#_9-2-1-前端压缩" aria-hidden="true">#</a></h4><p>前端压缩主要使用<code>UglifyJS</code>对 js 文件进行混淆、精简、合并和压缩。</p><p>比如对函数名、变量名、注释空格等进行简化和删除</p><p>在 webpack 中，可以使用如<code>uglifyjs-webpack-plugin,terser-webpack-plugin</code>等插件，或者 webpack 自带的工具(默认的 optimization.minimizer)进行操作</p><p>还有 css 资源 html 资源也可以进行适当压缩</p><p>具体请看<a href="https://webpack.docschina.org/configuration/optimization/#optimizationminimize" target="_blank" rel="noreferrer">webpack 优化配置项</a></p><h4 id="_9-2-2-服务端压缩" tabindex="-1">9.2.2 服务端压缩 <a class="header-anchor" href="#_9-2-2-服务端压缩" aria-hidden="true">#</a></h4><p>服务端压缩主要是使用<code>gzip</code>，gzip 对文本类资源压缩效率很高，当然 gzip 也会增加服务端的资源成本，当然适当使用还是很值得的。</p><p>nginx 配置如下：</p><div class="language-nginx"><button title="Copy Code" class="copy"></button><span class="lang">nginx</span><pre class="shiki material-theme-palenight" tabindex="0"><code><span class="line"><span style="color:#676E95;font-style:italic;">#nginx.conf中启用gzip</span></span>
<span class="line"><span style="color:#89DDFF;">gzip </span><span style="color:#A6ACCD;">on</span><span style="color:#89DDFF;">;</span></span>
<span class="line"><span style="color:#89DDFF;">gzip_types </span><span style="color:#A6ACCD;">application/javascript text/css text/plain</span><span style="color:#89DDFF;">;</span><span style="color:#A6ACCD;"> </span><span style="color:#676E95;font-style:italic;">#对jscss文件等进行压缩</span></span>
<span class="line"><span style="color:#89DDFF;">gzip_level</span><span style="color:#A6ACCD;"> 4</span><span style="color:#89DDFF;">;</span><span style="color:#A6ACCD;"> </span><span style="color:#676E95;font-style:italic;">#压缩机别，1-9，越大压缩越强</span></span>
<span class="line"><span style="color:#89DDFF;">gzip_buffers </span><span style="color:#A6ACCD;">4 </span><span style="color:#F78C6C;">16k</span><span style="color:#89DDFF;">;</span><span style="color:#A6ACCD;"> </span><span style="color:#676E95;font-style:italic;">#处理请求压缩的缓冲区数量和大小</span></span>
<span class="line"><span style="color:#89DDFF;">gzip_min_length </span><span style="color:#A6ACCD;">1k</span><span style="color:#89DDFF;">;</span><span style="color:#A6ACCD;"> </span><span style="color:#676E95;font-style:italic;">#文件大于1k才压缩</span></span>
<span class="line"><span style="color:#89DDFF;">gzip_http_version </span><span style="color:#A6ACCD;">1.1</span><span style="color:#89DDFF;">;</span><span style="color:#A6ACCD;"> </span><span style="color:#676E95;font-style:italic;">#只针对http版本1.1请求进行压缩</span></span>
<span class="line"></span></code></pre></div><h3 id="_9-3-懒加载" tabindex="-1">9.3 懒加载 <a class="header-anchor" href="#_9-3-懒加载" aria-hidden="true">#</a></h3><h4 id="_9-3-1-组件懒加载" tabindex="-1">9.3.1 组件懒加载 <a class="header-anchor" href="#_9-3-1-组件懒加载" aria-hidden="true">#</a></h4><p>React16.6 版本以后，可以使用<code>React.lazy</code>和<code>React.Suspense</code>，结合<code>import()</code>语法，进行异步引用 react 组件</p><p>16.6 之前可以使用<code>react-loadable</code>库</p><div class="language-js"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki material-theme-palenight" tabindex="0"><code><span class="line"><span style="color:#C792EA;">const</span><span style="color:#A6ACCD;"> AsyncComponent </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> React</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">lazy</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">=&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">import</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">./userComponent</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;">))</span></span>
<span class="line"><span style="color:#C792EA;">function</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">App</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#F07178;">  </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#F07178;"> (</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#89DDFF;">&lt;</span><span style="color:#FFCB6B;">React.Suspense</span><span style="color:#89DDFF;"> </span><span style="color:#C792EA;">fallback</span><span style="color:#89DDFF;">={&lt;</span><span style="color:#F07178;">div</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;">loading...</span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">div</span><span style="color:#89DDFF;">&gt;}&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">      </span><span style="color:#89DDFF;">&lt;</span><span style="color:#FFCB6B;">AsyncComponent</span><span style="color:#89DDFF;"> /&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">&lt;/</span><span style="color:#FFCB6B;">React.Suspense</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#F07178;">  )</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span>
<span class="line"></span></code></pre></div><h4 id="_9-3-2-路由懒加载" tabindex="-1">9.3.2 路由懒加载 <a class="header-anchor" href="#_9-3-2-路由懒加载" aria-hidden="true">#</a></h4><p>路由懒加载和组件类似，只是把整个路由组件使用<code>React.lazy</code>和<code>React.Suspense</code>进行异步引入。</p><div class="language-js"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki material-theme-palenight" tabindex="0"><code><span class="line"><span style="color:#C792EA;">const</span><span style="color:#A6ACCD;"> AsyncComponent </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> React</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">lazy</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">=&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">import</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">./userComponent</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;">))</span></span>
<span class="line"><span style="color:#C792EA;">function</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">App</span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#F07178;">  </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#F07178;"> (</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#89DDFF;">&lt;</span><span style="color:#FFCB6B;">React.Suspense</span><span style="color:#89DDFF;"> </span><span style="color:#C792EA;">fallback</span><span style="color:#89DDFF;">={&lt;</span><span style="color:#F07178;">div</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;">loading...</span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">div</span><span style="color:#89DDFF;">&gt;}&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">      </span><span style="color:#89DDFF;">&lt;</span><span style="color:#FFCB6B;">Switch</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">&lt;</span><span style="color:#FFCB6B;">Route</span><span style="color:#89DDFF;"> </span><span style="color:#C792EA;">exact</span><span style="color:#89DDFF;"> </span><span style="color:#C792EA;">path</span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">/user</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;"> </span><span style="color:#C792EA;">component</span><span style="color:#89DDFF;">={</span><span style="color:#A6ACCD;">AsyncComponent</span><span style="color:#89DDFF;">} /&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">      </span><span style="color:#89DDFF;">&lt;/</span><span style="color:#FFCB6B;">Switch</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">&lt;/</span><span style="color:#FFCB6B;">React.Suspense</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#F07178;">  )</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span>
<span class="line"></span></code></pre></div><h3 id="_9-4-按需引入" tabindex="-1">9.4 按需引入 <a class="header-anchor" href="#_9-4-按需引入" aria-hidden="true">#</a></h3><h4 id="_9-4-1-tree-shaking-垃圾代码净化" tabindex="-1">9.4.1 Tree Shaking--垃圾代码净化 <a class="header-anchor" href="#_9-4-1-tree-shaking-垃圾代码净化" aria-hidden="true">#</a></h4><p>webpack 提供的术语，主要利用 es6 模块系统中的静态结构特性，即<code>import和export</code>，对引入但未被使用的模块进行标记删除</p><p>在 webpack 中，你可以在<code>package.json</code>中添加<code>sideEffects:false</code>或者一个有副作用的文件数组，这样告知 webpack 可以安全删除未使用到的 export</p><div class="language-js"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki material-theme-palenight" tabindex="0"><code><span class="line"><span style="color:#676E95;font-style:italic;">// package.json</span></span>
<span class="line"><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#F07178;">  </span><span style="color:#FFCB6B;">sideEffects</span><span style="color:#89DDFF;">:</span><span style="color:#F07178;"> </span><span style="color:#FF9CAC;">false</span><span style="color:#89DDFF;">,</span></span>
<span class="line"><span style="color:#89DDFF;">  </span><span style="color:#676E95;font-style:italic;">// sideEffects:[&quot;./src/user.js&quot;]</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span>
<span class="line"></span></code></pre></div><h4 id="_9-4-2-部分引入" tabindex="-1">9.4.2 部分引入 <a class="header-anchor" href="#_9-4-2-部分引入" aria-hidden="true">#</a></h4><p>如对 Lodash、momentjs 等工具库，进行部分引入，而不是全量</p><div class="language-js"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki material-theme-palenight" tabindex="0"><code><span class="line"><span style="color:#89DDFF;font-style:italic;">import</span><span style="color:#A6ACCD;"> isNumber </span><span style="color:#89DDFF;font-style:italic;">from</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">lodash/isNumber</span><span style="color:#89DDFF;">&quot;</span></span>
<span class="line"><span style="color:#89DDFF;font-style:italic;">import</span><span style="color:#A6ACCD;"> debounce </span><span style="color:#89DDFF;font-style:italic;">from</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">lodash/debounce</span><span style="color:#89DDFF;">&quot;</span></span>
<span class="line"></span></code></pre></div><p>针对 momentjs 去掉语言包，只引入使用到的语言</p><div class="language-js"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki material-theme-palenight" tabindex="0"><code><span class="line"><span style="color:#676E95;font-style:italic;">// webpack配置</span></span>
<span class="line"><span style="color:#89DDFF;">new</span><span style="color:#A6ACCD;"> webpack</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">IgnorePlugin</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">/</span><span style="color:#89DDFF;font-style:italic;">^</span><span style="color:#A6ACCD;">\.\/</span><span style="color:#C3E88D;">locale</span><span style="color:#89DDFF;font-style:italic;">$</span><span style="color:#89DDFF;">/</span><span style="color:#89DDFF;">,</span><span style="color:#C3E88D;"> </span><span style="color:#89DDFF;">/</span><span style="color:#C3E88D;">moment</span><span style="color:#89DDFF;font-style:italic;">$</span><span style="color:#89DDFF;">/</span><span style="color:#A6ACCD;">)</span></span>
<span class="line"><span style="color:#676E95;font-style:italic;">// 使用时引入中文包</span></span>
<span class="line"><span style="color:#89DDFF;font-style:italic;">import</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">moment/locale/zh-cn</span><span style="color:#89DDFF;">&quot;</span></span>
<span class="line"><span style="color:#A6ACCD;">moment</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">locale</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">zh-cn</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;">)</span></span>
<span class="line"></span></code></pre></div><h3 id="_9-5-负载均衡" tabindex="-1">9.5 负载均衡 <a class="header-anchor" href="#_9-5-负载均衡" aria-hidden="true">#</a></h3><p>负载均衡可以减小单台服务器压力，同时实现高可用。</p><p>更可以水平扩展或收缩，灵活动态增减服务器数量</p><p>要明白负载均衡，现需要知道一个知识点，OSI 七层模型(参考模型)：</p><div class="language-mermaid"><button title="Copy Code" class="copy"></button><span class="lang">mermaid</span><pre class="shiki material-theme-palenight" tabindex="0"><code><span class="line"><span style="color:#89DDFF;font-style:italic;">graph</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">LR</span></span>
<span class="line"></span>
<span class="line"><span style="color:#A6ACCD;">应用层</span><span style="color:#89DDFF;font-style:italic;">--&gt;</span><span style="color:#A6ACCD;">表示层</span><span style="color:#89DDFF;font-style:italic;">--&gt;</span><span style="color:#A6ACCD;">会话层</span><span style="color:#89DDFF;font-style:italic;">--&gt;</span><span style="color:#A6ACCD;">传输层</span><span style="color:#89DDFF;font-style:italic;">--&gt;</span><span style="color:#A6ACCD;">网络层</span><span style="color:#89DDFF;font-style:italic;">--&gt;</span><span style="color:#A6ACCD;">数据链路层</span><span style="color:#89DDFF;font-style:italic;">--&gt;</span><span style="color:#A6ACCD;">物理层</span></span>
<span class="line"></span></code></pre></div><p>osi 七层和 tcp/ip 五层模型示意图：</p><p><img src="/assets/0dd7912397dda144d48ab350bbb7d0a20df48655.e493b195.jpeg" alt="img"></p><h4 id="_9-5-1-负载均衡分类" tabindex="-1">9.5.1 负载均衡分类 <a class="header-anchor" href="#_9-5-1-负载均衡分类" aria-hidden="true">#</a></h4><p>最常用的负载均衡是四层负载均衡和七层负载均衡</p><ul><li>四层负载均衡：基于 IP 和端口的负载均衡</li><li>七层负载均衡：基于 URL 等应用信息的负载均衡</li></ul><h4 id="_9-5-2-负载均衡工具" tabindex="-1">9.5.2 负载均衡工具 <a class="header-anchor" href="#_9-5-2-负载均衡工具" aria-hidden="true">#</a></h4><h5 id="_1-nginx-工具" tabindex="-1">1.nginx 工具 <a class="header-anchor" href="#_1-nginx-工具" aria-hidden="true">#</a></h5><p>nginx 可用于四层和七层负载均衡。</p><p>典型架构如下：</p><div class="language-mermaid"><button title="Copy Code" class="copy"></button><span class="lang">mermaid</span><pre class="shiki material-theme-palenight" tabindex="0"><code><span class="line"><span style="color:#89DDFF;font-style:italic;">graph</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">TD</span></span>
<span class="line"><span style="color:#A6ACCD;">用户访问 </span><span style="color:#89DDFF;font-style:italic;">--&gt;</span><span style="color:#A6ACCD;"> Nginx</span></span>
<span class="line"><span style="color:#A6ACCD;">Nginx</span><span style="color:#89DDFF;font-style:italic;">--&gt;</span><span style="color:#A6ACCD;">WebServer1</span></span>
<span class="line"><span style="color:#A6ACCD;">Nginx</span><span style="color:#89DDFF;font-style:italic;">--&gt;</span><span style="color:#A6ACCD;">WebServer2</span></span>
<span class="line"><span style="color:#A6ACCD;">Nginx</span><span style="color:#89DDFF;font-style:italic;">--&gt;</span><span style="color:#A6ACCD;">WebServer3</span></span>
<span class="line"></span></code></pre></div><h5 id="_2-lvs-工具" tabindex="-1">2.lvs 工具 <a class="header-anchor" href="#_2-lvs-工具" aria-hidden="true">#</a></h5><p>LVS（linux virtual server,linux 虚拟服务器），主要用于四层负载均衡</p><p>典型架构如下：</p><div class="language-mermaid"><button title="Copy Code" class="copy"></button><span class="lang">mermaid</span><pre class="shiki material-theme-palenight" tabindex="0"><code><span class="line"><span style="color:#89DDFF;font-style:italic;">graph</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">TD</span></span>
<span class="line"><span style="color:#A6ACCD;">用户访问 </span><span style="color:#89DDFF;font-style:italic;">--&gt;</span><span style="color:#A6ACCD;"> LVS</span></span>
<span class="line"><span style="color:#A6ACCD;">LVS </span><span style="color:#89DDFF;font-style:italic;">--&gt;</span><span style="color:#A6ACCD;"> Nginx1</span></span>
<span class="line"><span style="color:#A6ACCD;">LVS </span><span style="color:#89DDFF;font-style:italic;">--&gt;</span><span style="color:#A6ACCD;"> Nginx2</span></span>
<span class="line"><span style="color:#A6ACCD;">Nginx1</span><span style="color:#89DDFF;font-style:italic;">--&gt;</span><span style="color:#A6ACCD;">WebServer1</span></span>
<span class="line"><span style="color:#A6ACCD;">Nginx1</span><span style="color:#89DDFF;font-style:italic;">--&gt;</span><span style="color:#A6ACCD;">WebServer2</span></span>
<span class="line"><span style="color:#A6ACCD;">Nginx2</span><span style="color:#89DDFF;font-style:italic;">--&gt;</span><span style="color:#A6ACCD;">WebServer3</span></span>
<span class="line"><span style="color:#A6ACCD;">Nginx2</span><span style="color:#89DDFF;font-style:italic;">--&gt;</span><span style="color:#A6ACCD;">WebServer4</span></span>
<span class="line"></span></code></pre></div><h5 id="_3-haproxy-工具" tabindex="-1">3.HAProxy 工具 <a class="header-anchor" href="#_3-haproxy-工具" aria-hidden="true">#</a></h5><p>与 nginx 一样，可用于七层和四层负载均衡</p><h4 id="_9-5-4-负载均衡实现" tabindex="-1">9.5.4 负载均衡实现 <a class="header-anchor" href="#_9-5-4-负载均衡实现" aria-hidden="true">#</a></h4><p>负载均衡服务器基于某种策略将请求转发到不同的服务器上，是通过负载均衡算法实现的。</p><p>负载均衡算法分静态和动态两类</p><ul><li>静态分配算法：以固定的概率分配任务，不考虑服务器状态信息，如轮询算法、加权轮转算法</li><li>动态分配算法：以服务器的实时负载状态信息来决定任务的分配，如最小连接数、动态性能分配等</li></ul><h3 id="_9-6-cdn-简介" tabindex="-1">9.6 CDN 简介 <a class="header-anchor" href="#_9-6-cdn-简介" aria-hidden="true">#</a></h3><p>CDN（Content Delivery Network），即内容分发网络</p><p>CDN 可以解决因分布、带宽、服务器性能带来的访问延时问题，适用于站点加速、点播、直播等场景，使用户就近获取资源，提升网站的加载速度，提升体验</p><h4 id="_9-6-1-工作过程" tabindex="-1">9.6.1 工作过程 <a class="header-anchor" href="#_9-6-1-工作过程" aria-hidden="true">#</a></h4><p><a href="https://www.mk2048.com/blog/blog_hcick2bkbachj.html" target="_blank" rel="noreferrer">参考文章 cdn 原理</a></p><p>未使用 CDN 之前，传统网站工作过程如下：</p><ul><li>用户浏览器输入域名</li><li>浏览器向本地 DNS 服务器请求该域名的解析，如果有缓存解析结果，则直接返回</li><li>如果没有，则以递归或迭代的方式向整个 DNS 系统请求解析</li><li>浏览器得到解析结果，即网站域名对应的 ip 地址</li><li>浏览器与服务器建立 TCP 连接</li><li>浏览器发送 HTTP 请求</li><li>浏览器接收服务器返回的内容</li><li>浏览器与服务器断开 TCP 连接</li></ul><p>使用了 CDN 之后，请求过程会改为如下方式：</p><ul><li>域名经过本地 DNS 系统解析，如果没有相应域名的缓存，则本地 DNS 系统会将域名的解析交给 CNAME 指向的 CDN 专用 DNS 服务器</li><li>DNS 服务器将 CDN 的全局负载均衡设备的 IP 地址返回给浏览器</li><li>浏览器向全局负载均衡设备发起请求</li><li>全局负载均衡设备根据用户 ip 地址及用户请求的 URL，选择一台用户所在区域的区域负载均衡设备，并将请求转发给它</li><li>基于一些综合分析后，区域负载均衡设备会选一个最优的缓存服务器节点，并将此缓存服务器的 IP 地址返回给全局负载均衡设备</li><li>全局负载均衡设备把缓存服务器的 IP 地址返回给浏览器（重定向？）</li><li>浏览器向缓存服务器发送请求，缓存服务器响应请求。如果这台缓存服务器并没有对应的资源，则会向上一级缓存服务器请求内容，直至追溯到网站的源服务器并将资源拉取到本地</li></ul><p>CDN 全局负载均衡设备和区域均衡设备根据用户的 ip 地址，将域名解析成节点中缓存服务器的 ip 地址，实现用户就近访问，从而提高服务器的响应速度。</p><p>对于静态资源来说，个人用户可以使用免费的 cdn 加速服务，如 BootCDN、CDNNJS 等</p><h2 id="第-10-章-服务端渲染" tabindex="-1">第 10 章 服务端渲染 <a class="header-anchor" href="#第-10-章-服务端渲染" aria-hidden="true">#</a></h2><h3 id="_10-1-区别和优缺点" tabindex="-1">10.1 区别和优缺点 <a class="header-anchor" href="#_10-1-区别和优缺点" aria-hidden="true">#</a></h3><p>服务端渲染和客户端渲染区别：</p><ul><li>客户端渲染：服务端返回初始 html，再由 js 异步加载数据，完成最终页面渲染</li><li>服务端渲染：服务端返回页面完整 html 和数据，省去了异步加载数据和渲染逻辑</li></ul><p>服务端渲染优点：</p><ul><li>服务端渲染返回渲染好的 html 文档，可以让首屏较快展现给用户</li><li>便于搜索引擎抓取和分析页面，利于 SEO</li></ul><p>服务端渲染缺点：</p><ul><li>每次页面加载服务器都需要渲染完整的页面和资源，增加服务器压力</li><li>同时进行多个页面跳转，体验没有客户端渲染好</li></ul><p>客户端缺点：</p><ul><li>首屏渲染慢,下载 js 文件，执行 js，js 再异步加载数据渲染页面，根据网速快慢等待时间不同</li><li>seo 能力差：js 动态生成的内容，搜索引擎识别还比较弱</li></ul><p>怎么选择呢？</p><ul><li>公司和产品网站、信息类和新闻类网站等需要考虑 seo 的，优先服务端渲染</li><li>除上面以外，都可以使用客户端渲染</li></ul><h3 id="_10-2-react-服务端渲染" tabindex="-1">10.2 React 服务端渲染 <a class="header-anchor" href="#_10-2-react-服务端渲染" aria-hidden="true">#</a></h3><h4 id="_10-2-1-组件渲染" tabindex="-1">10.2.1 组件渲染 <a class="header-anchor" href="#_10-2-1-组件渲染" aria-hidden="true">#</a></h4><p>使用 react 服务端渲染，主要原理是利用 react 的服务端渲染 api，把 react 组件生成字符串，然后返回给客户端</p><p>如下面的简单示例：</p><div class="language-js"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki material-theme-palenight" tabindex="0"><code><span class="line"><span style="color:#676E95;font-style:italic;">// src/index.js</span></span>
<span class="line"><span style="color:#C792EA;">const</span><span style="color:#A6ACCD;"> express </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">require</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">express</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">)</span></span>
<span class="line"><span style="color:#C792EA;">const</span><span style="color:#A6ACCD;"> React </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">require</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">react</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">)</span></span>
<span class="line"><span style="color:#C792EA;">const</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span><span style="color:#A6ACCD;">renderToString</span><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">require</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">react-dom/server</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">)</span></span>
<span class="line"><span style="color:#C792EA;">const</span><span style="color:#A6ACCD;"> HelloSSR </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">require</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">./components/HelloSSR</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">)</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">default</span></span>
<span class="line"><span style="color:#C792EA;">const</span><span style="color:#A6ACCD;"> app </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">express</span><span style="color:#A6ACCD;">()</span></span>
<span class="line"><span style="color:#A6ACCD;">app</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">get</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">/</span><span style="color:#89DDFF;">&#39;</span><span style="color:#89DDFF;">,(</span><span style="color:#A6ACCD;font-style:italic;">req</span><span style="color:#89DDFF;">,</span><span style="color:#A6ACCD;font-style:italic;">res</span><span style="color:#89DDFF;">)</span><span style="color:#C792EA;">=&gt;</span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#F07178;">  </span><span style="color:#C792EA;">const</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">str</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">=</span><span style="color:#F07178;"> </span><span style="color:#82AAFF;">renderToString</span><span style="color:#F07178;">(</span><span style="color:#89DDFF;">&lt;</span><span style="color:#FFCB6B;">HelloSSR</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;">)</span></span>
<span class="line"><span style="color:#A6ACCD;">  res.send(`</span></span>
<span class="line"><span style="color:#A6ACCD;">  &lt;!DOCTYPE html&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">html</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">head</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">title</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;">ssr</span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">title</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">head</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">body</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">div</span><span style="color:#89DDFF;"> </span><span style="color:#C792EA;">id</span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">root</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">      $</span><span style="color:#89DDFF;">{</span><span style="color:#A6ACCD;">str</span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">div</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">body</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">  </span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">html</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">  `)</span></span>
<span class="line"><span style="color:#A6ACCD;">})</span></span>
<span class="line"><span style="color:#A6ACCD;">app.listen(3000,()=&gt;</span><span style="color:#89DDFF;">{</span><span style="color:#A6ACCD;">console</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">log</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">server is running on port 3000</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">)</span><span style="color:#89DDFF;">}</span><span style="color:#A6ACCD;">)</span></span>
<span class="line"></span></code></pre></div><h4 id="_10-2-2-同构" tabindex="-1">10.2.2 同构 <a class="header-anchor" href="#_10-2-2-同构" aria-hidden="true">#</a></h4><h5 id="_1-什么是同构" tabindex="-1">1.什么是同构 <a class="header-anchor" href="#_1-什么是同构" aria-hidden="true">#</a></h5><p>同构，就是同样的代码在服务端运行一次，再在客户端运行一次，同构后，组件在浏览器端完成挂载后，react 会自动完成事件绑定等操作</p><h5 id="_2-同构目的" tabindex="-1">2.同构目的 <a class="header-anchor" href="#_2-同构目的" aria-hidden="true">#</a></h5><p>为什么要同构呢？因为服务端 react 运行时是无法进行事件绑定等操作的，即没法执行和 dom 相关的操作，所以返回给浏览器端后，按钮点击是无效的，这时候就需要 react 在浏览器端再执行一次，绑定事件。</p><h5 id="_3-会不会重复渲染" tabindex="-1">3.会不会重复渲染？ <a class="header-anchor" href="#_3-会不会重复渲染" aria-hidden="true">#</a></h5><p>这样会不会造成重复渲染？不会，浏览器挂载页面后，react-dom 在渲染组件前，会和页面中的节点做对比，只有匹配不上的才会重新渲染，已有节点会复用。</p><h5 id="_4-怎么做" tabindex="-1">4.怎么做？ <a class="header-anchor" href="#_4-怎么做" aria-hidden="true">#</a></h5><p>道理很简单，即在服务端渲染的页面，引入打包后客户端运行的 react 代码，其实和客户端渲染代码差不多，只不过用<code>hydrate()</code>替代<code>render()</code>，hydrate 的作用是当 ReactDOM 复用 reactDOMServer 渲染的内容时，会尽可能保持其结构，并补充事件绑定等客户端特有内容。</p><div class="language-js"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki material-theme-palenight" tabindex="0"><code><span class="line"><span style="color:#676E95;font-style:italic;">// src/client/index.js</span></span>
<span class="line"><span style="color:#89DDFF;font-style:italic;">import</span><span style="color:#A6ACCD;"> ReactDOM </span><span style="color:#89DDFF;font-style:italic;">from</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">react-dom</span><span style="color:#89DDFF;">&#39;</span></span>
<span class="line"><span style="color:#C792EA;">const</span><span style="color:#A6ACCD;"> HelloSSR </span><span style="color:#89DDFF;">=()</span><span style="color:#C792EA;">=&gt;</span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#F07178;">  </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#F07178;"> (</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">div</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">      </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">div</span><span style="color:#89DDFF;">&gt;</span><span style="color:#A6ACCD;">hello ssr</span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">div</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">      </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">button</span><span style="color:#89DDFF;"> </span><span style="color:#C792EA;">onClick</span><span style="color:#89DDFF;">={()</span><span style="color:#C792EA;">=&gt;</span><span style="color:#82AAFF;">alert</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">点击</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">])</span><span style="color:#89DDFF;">}&gt;</span><span style="color:#A6ACCD;">点我</span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">button</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">div</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#F07178;">  )</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">ReactDOM</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">hydrate</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">&lt;</span><span style="color:#FFCB6B;">HelloSSR</span><span style="color:#89DDFF;">/&gt;,</span><span style="color:#A6ACCD;">document</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getElementById</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">&#39;</span><span style="color:#C3E88D;">root</span><span style="color:#89DDFF;">&#39;</span><span style="color:#A6ACCD;">))</span></span>
<span class="line"></span></code></pre></div><div class="language-js"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki material-theme-palenight has-highlighted-lines" tabindex="0"><code><span class="line"><span style="color:#676E95;font-style:italic;">// src/index.js部分修改</span></span>
<span class="line"><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">div</span><span style="color:#89DDFF;"> </span><span style="color:#C792EA;">id</span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">root</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">      $</span><span style="color:#89DDFF;">{</span><span style="color:#A6ACCD;">str</span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">div</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line highlighted"><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">script</span><span style="color:#89DDFF;"> </span><span style="color:#C792EA;">src</span><span style="color:#89DDFF;">=</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">./dist/bundle.js</span><span style="color:#89DDFF;">&quot;</span><span style="color:#89DDFF;">&gt;&lt;/</span><span style="color:#F07178;">script</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"></span></code></pre></div><h5 id="_5-完整流程" tabindex="-1">5.完整流程 <a class="header-anchor" href="#_5-完整流程" aria-hidden="true">#</a></h5><p>同构的完整流程大概如下：</p><ul><li>1.服务器运行 react 代码渲染出 html</li><li>2.服务器将 html 返回给浏览器</li><li>3.浏览器渲染页面并加载打包后的客户端 js</li><li>4.执行客户端 reactjs 代码</li><li>5.客户端 reactjs 代码接管页面相关操作</li></ul><h4 id="_10-2-3-路由" tabindex="-1">10.2.3 路由 <a class="header-anchor" href="#_10-2-3-路由" aria-hidden="true">#</a></h4><p>服务端渲染的路由和客户端路由共用，只是服务端最外层 Router 应该使用<code>StaticRouter</code>而不是<code>BrowserRouter或HashRouter</code></p><h4 id="_10-2-4-状态" tabindex="-1">10.2.4 状态 <a class="header-anchor" href="#_10-2-4-状态" aria-hidden="true">#</a></h4><p>服务端和客户端的数据流管理完全一样，使用 react-redux 就 ok</p><h3 id="_10-3-next-js-服务端渲染" tabindex="-1">10.3 Next.js 服务端渲染 <a class="header-anchor" href="#_10-3-next-js-服务端渲染" aria-hidden="true">#</a></h3><p>从上面可知，要自己动手配置一个服务端渲染开发流程，挺麻烦的。</p><p><code>Next.js</code>(<a href="https://nextjs.org" target="_blank" rel="noreferrer">https://nextjs.org</a>) 就是能够简化这些技术细节，让我们专注于项目和业务的实现。</p><h4 id="_10-3-1-next-js-简介" tabindex="-1">10.3.1 next.js 简介 <a class="header-anchor" href="#_10-3-1-next-js-简介" aria-hidden="true">#</a></h4><p>轻量级的 react 服务端渲染框架，提升开发体验，有如下特点：</p><ul><li>开箱即用的服务端渲染，包含自动打包编译、热加载</li><li>基于文件系统的路由，代码分层结构良好</li><li>自动代码分片，不会加载不需要的代码</li><li>seo 友好，html 直接输出</li></ul><h4 id="_10-3-2-使用-next-js" tabindex="-1">10.3.2 使用 next.js <a class="header-anchor" href="#_10-3-2-使用-next-js" aria-hidden="true">#</a></h4><h5 id="_1-安装和初始化" tabindex="-1">1.安装和初始化 <a class="header-anchor" href="#_1-安装和初始化" aria-hidden="true">#</a></h5><div class="language-shell"><button title="Copy Code" class="copy"></button><span class="lang">shell</span><pre class="shiki material-theme-palenight" tabindex="0"><code><span class="line"><span style="color:#FFCB6B;">npm</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">install</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">--save</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">next</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">react</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">react-dom</span></span>
<span class="line"></span></code></pre></div><p>其中 next.js 会根据 pages 文件夹下的.js 文件生成相应路由，自动处理和渲染。其中<code>pages/index.js</code>对应路由<code>/</code>，其它类似，如<code>pages/list.js</code>对应路由<code>/list</code></p><h5 id="_2-异步请求数据" tabindex="-1">2.异步请求数据 <a class="header-anchor" href="#_2-异步请求数据" aria-hidden="true">#</a></h5><p>next.js 提供<code>getinitialProps</code>特性，用于异步获取数据</p><div class="language-js"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki material-theme-palenight" tabindex="0"><code><span class="line"><span style="color:#C792EA;">const</span><span style="color:#A6ACCD;"> List </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#A6ACCD;font-style:italic;">props</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">=&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#F07178;">  </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#F07178;"> (</span></span>
<span class="line"><span style="color:#F07178;">    </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">div</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">      </span><span style="color:#89DDFF;">{</span><span style="color:#A6ACCD;">props</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">list</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">map</span><span style="color:#A6ACCD;">(</span><span style="color:#A6ACCD;font-style:italic;">l</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">=&gt;</span><span style="color:#A6ACCD;"> (</span></span>
<span class="line"><span style="color:#A6ACCD;">        </span><span style="color:#89DDFF;">&lt;</span><span style="color:#F07178;">li</span><span style="color:#89DDFF;"> </span><span style="color:#C792EA;">key</span><span style="color:#89DDFF;">={</span><span style="color:#A6ACCD;">l</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">id</span><span style="color:#89DDFF;">}&gt;{</span><span style="color:#A6ACCD;">l</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">name</span><span style="color:#89DDFF;">}&lt;/</span><span style="color:#F07178;">li</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#A6ACCD;">      ))</span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">    </span><span style="color:#89DDFF;">&lt;/</span><span style="color:#F07178;">div</span><span style="color:#89DDFF;">&gt;</span></span>
<span class="line"><span style="color:#F07178;">  )</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#A6ACCD;">List</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">getInitialProps</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">async</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">()</span><span style="color:#A6ACCD;"> </span><span style="color:#C792EA;">=&gt;</span><span style="color:#A6ACCD;"> </span><span style="color:#89DDFF;">{</span></span>
<span class="line"><span style="color:#F07178;">  </span><span style="color:#C792EA;">const</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">res</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">=</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;font-style:italic;">await</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">http</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">get</span><span style="color:#F07178;">(</span><span style="color:#89DDFF;">{}</span><span style="color:#F07178;">)</span></span>
<span class="line"><span style="color:#F07178;">  </span><span style="color:#89DDFF;font-style:italic;">return</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">{</span><span style="color:#F07178;"> list</span><span style="color:#89DDFF;">:</span><span style="color:#F07178;"> </span><span style="color:#A6ACCD;">res</span><span style="color:#89DDFF;">.</span><span style="color:#A6ACCD;">data</span><span style="color:#F07178;"> </span><span style="color:#89DDFF;">}</span></span>
<span class="line"><span style="color:#89DDFF;">}</span></span>
<span class="line"></span></code></pre></div><p>getinitialProps 方法中必须返回对象，并且只能在 pages 页面中使用</p><h3 id="_10-4-seo-搜索引擎优化" tabindex="-1">10.4 seo-搜索引擎优化 <a class="header-anchor" href="#_10-4-seo-搜索引擎优化" aria-hidden="true">#</a></h3><p>除了传统 seo 技巧，react 中可以提一下的还有<code>React Helmet</code>和预渲染</p><h4 id="_10-4-1-react-helmet-组件" tabindex="-1">10.4.1 React Helmet 组件 <a class="header-anchor" href="#_10-4-1-react-helmet-组件" aria-hidden="true">#</a></h4><p>可重用的 react 组件，用来管理页面的 head 标签</p><p>例如本站 gatsby 搭建的网站，就是用了该组件</p><h4 id="_10-4-2-预渲染" tabindex="-1">10.4.2 预渲染 <a class="header-anchor" href="#_10-4-2-预渲染" aria-hidden="true">#</a></h4><p>除了使用服务端渲染优化 seo 外，还可以使用预渲染技术</p><p>预渲染，其实就是在客户访问或蜘蛛爬取时，返回完整的 html 内容。这种提前将完整内容渲染给用户的技术就是预渲染。</p><p>怎么做呢？</p><p>比如有<code>Prerender</code>(<a href="https://prerender.io" target="_blank" rel="noreferrer">https://prerender.io</a>)实现，它是一个 Node 服务，使用 Headless Chrome(chrome 浏览器的无界面版)，它会监听 http 请求，获取 url 并加载到 headless 中，完成页面渲染，并返回给用户。</p><p>安装 prerender</p><div class="language-shell"><button title="Copy Code" class="copy"></button><span class="lang">shell</span><pre class="shiki material-theme-palenight" tabindex="0"><code><span class="line"><span style="color:#FFCB6B;">npm</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">install</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">--save</span><span style="color:#A6ACCD;"> </span><span style="color:#C3E88D;">prerender</span></span>
<span class="line"></span></code></pre></div><p>启动 node 服务</p><div class="language-js"><button title="Copy Code" class="copy"></button><span class="lang">js</span><pre class="shiki material-theme-palenight" tabindex="0"><code><span class="line"><span style="color:#676E95;font-style:italic;">//server.js</span></span>
<span class="line"><span style="color:#C792EA;">const</span><span style="color:#A6ACCD;"> prerender </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">require</span><span style="color:#A6ACCD;">(</span><span style="color:#89DDFF;">&quot;</span><span style="color:#C3E88D;">prerender</span><span style="color:#89DDFF;">&quot;</span><span style="color:#A6ACCD;">)</span></span>
<span class="line"><span style="color:#C792EA;">const</span><span style="color:#A6ACCD;"> server </span><span style="color:#89DDFF;">=</span><span style="color:#A6ACCD;"> </span><span style="color:#82AAFF;">prerender</span><span style="color:#A6ACCD;">()</span></span>
<span class="line"><span style="color:#A6ACCD;">server</span><span style="color:#89DDFF;">.</span><span style="color:#82AAFF;">start</span><span style="color:#A6ACCD;">()</span></span>
<span class="line"></span></code></pre></div><div class="language-shell"><button title="Copy Code" class="copy"></button><span class="lang">shell</span><pre class="shiki material-theme-palenight" tabindex="0"><code><span class="line"><span style="color:#A6ACCD;">PORT</span><span style="color:#89DDFF;">=</span><span style="color:#F78C6C;">8000</span><span style="color:#A6ACCD;"> node server.js</span></span>
<span class="line"></span></code></pre></div><p>访问并带上要渲染的 url</p><div class="language-shell"><button title="Copy Code" class="copy"></button><span class="lang">shell</span><pre class="shiki material-theme-palenight" tabindex="0"><code><span class="line"><span style="color:#FFCB6B;">http://localhost:8000/render?url=http://localhost:3000</span></span>
<span class="line"></span></code></pre></div><hr><p>整篇笔记完成</p></div></div></main><!--[--><!--]--><footer class="VPDocFooter" data-v-c5936a1e data-v-e033cd21><div class="edit-info" data-v-e033cd21><!----><div class="last-updated" data-v-e033cd21><p class="VPLastUpdated" data-v-e033cd21 data-v-355aa5ef>更新时间: <time datetime="2023-03-01T15:45:52.000Z" data-v-355aa5ef></time></p></div></div><!----></footer><!--[--><!--]--></div></div></div></div></div><!----><!--[--><!--]--></div></div>
    <script>__VP_HASH_MAP__ = JSON.parse("{\"about_me.md\":\"a6963ed5\",\"about_contact.md\":\"753dd087\",\"blog_interviews_project-for-frontend-interview-questions-collection.md\":\"3ac3f621\",\"blog_svg_jiaohu.md\":\"bdc89024\",\"index.md\":\"ac84e241\",\"blog_svg_lujing.md\":\"2247252e\",\"blog_svg_lvjing.md\":\"7fb98f9e\",\"blog_svg_rumen.md\":\"760fcfc8\",\"blog_svg_tuanhejianbian.md\":\"1d67f731\",\"blog_svg_userspaceonuse-and-objectboundingbox.md\":\"7135675e\",\"blog_svg_wendangjiegou.md\":\"659e2114\",\"english_how.md\":\"47ca54fc\",\"blog_svg_zuobiaoxitong.md\":\"9f073aa7\",\"blog_svg_zuobiaoxitongbianhuan.md\":\"09c91562\",\"english_howtolearnword.md\":\"29e67a9c\",\"english_plan.md\":\"d3863f0d\",\"english_string.md\":\"afd02e54\",\"blog_interviews_css-for-frontend-interview-questions-collection.md\":\"ad8ec08c\",\"blog_interviews_index.md\":\"7922302b\",\"blog_interviews_javascript-for-frontend-interview-questions-collection.md\":\"1f97ba08\",\"blog_interviews_mian-shi-xin-de.md\":\"f249f58c\",\"blog_interviews_pingtouge-interview-questions-collection.md\":\"01898e13\",\"life_car_baoxian.md\":\"1a0484fd\",\"life_index.md\":\"294ee10b\",\"life_car_jiaotongshiguchuli.md\":\"2dfb478a\",\"life_car_kaiche.md\":\"fb68bff5\",\"photograph_fujifilm_renxiangpeifang.md\":\"df9d71f0\",\"photograph_fujifilm_jiappianjichu.md\":\"e4797107\",\"blog_readingnotes_browserprinciple.md\":\"5600b029\",\"blog_interviews_webpack-for-frontend-interview-questions-collection.md\":\"5e087073\",\"photograph_fujifilm_fengguangpeifang.md\":\"1f3c5cc8\",\"blog_summary_20210702.md\":\"992c6ebc\",\"blog_summary_20210716.md\":\"27df70fd\",\"blog_summary_20210723.md\":\"b3df6f63\",\"blog_summary_20210730.md\":\"0fd6c31f\",\"blog_readingnotes_jsgc4-2.md\":\"754107dd\",\"blog_summary_20210806.md\":\"8ede013a\",\"blog_svg_caijianhemengban.md\":\"345a71fa\",\"blog_readingnotes_jsgc4.md\":\"badec6ec\",\"blog_2020_07_accumulation-fund-transform.md\":\"ea36b833\",\"blog_svg_jibenxingzhuang.md\":\"3d07ebc2\",\"photograph_home.md\":\"289914d9\",\"photograph_fujifilm_xiangjishezhi.md\":\"c859f451\",\"photograph_sheyingzhishi.md\":\"7db1fab8\",\"photograph_xiangjixuanze.md\":\"b38cb6f4\",\"wemedia_what.md\":\"1d28f58c\",\"wemedia_emoji.md\":\"234e5631\",\"blog_2020_07_css-modules-docs.md\":\"4575442d\",\"blog_2020_07_git-docs.md\":\"22c9808c\",\"blog_2020_07_http-cache-and-nginx-config.md\":\"82b01b17\",\"blog_svg_donghua.md\":\"87326708\",\"blog_2020_06_gatsby-markdown-blog-system.md\":\"87de2558\",\"blog_2020_06_ubuntu14.04-install-nginx-for-source-code.md\":\"eff6df3c\",\"blog_2020_07_throttle-and-debounce.md\":\"745f9ff3\",\"blog_2020_07_regex-in-javascript.md\":\"375103f6\",\"blog_finance_income.md\":\"90b9a5cf\",\"blog_interviews_safe-for-frontend-interview-questions-collection.md\":\"6ae808c2\",\"blog_2020_07_vim-docs.md\":\"50a62a76\",\"blog_2020_08_fiddler.md\":\"29bf9a9b\",\"blog_2020_11_webpack-how-to-load-chunks.md\":\"2fc96bb0\",\"blog_2020_07_use-react-framework-web-site.md\":\"f836a6bd\",\"blog_interviews_algorithm-javascript.md\":\"72bdd42b\",\"blog_summary_20210611.md\":\"4cc8a1ab\",\"blog_summary_20210529.md\":\"fb886442\",\"blog_interviews_babel-for-frontend-interview-questions-collection.md\":\"495cb8da\",\"blog_summary_20210124.md\":\"ee3946e0\",\"blog_summary_20210619.md\":\"ef11c68e\",\"blog_summary_20210626.md\":\"5d8d583c\",\"blog_svg_wenben.md\":\"4aaeb73b\",\"blog_interviews_html-for-frontend-interview-questions-collection.md\":\"78197e40\",\"blog_interviews_react-for-frontend-interview-questions-collection.md\":\"90e3af48\",\"blog_interviews_http-for-frontend-interview-questions-collection.md\":\"1129730d\",\"blog_frontend_library.md\":\"34c972ff\",\"blog_readingnotes_reactandnodejs.md\":\"8a99114b\"}")</script>
    <script type="module" async src="/assets/app.6facf8ba.js"></script>
    
  </body>
</html>